繁体   English   中英

递归,堆栈和缓存未命中

[英]Recursion, the stack and cache misses

想象一下我需要遍历一棵树。 据我了解,如果我以递归方式进行操作,则每个递归函数调用都需要将其本地参数保存在堆栈帧中。 堆栈帧驻留在堆栈存储器中,并且每个帧都由堆栈指针指向。

堆栈存储器什么时候加载到CPU缓存中? 每次函数返回? 例如,如果我要进行很多递归函数调用,它将“破坏”我的CPU缓存吗?

在遍历树时,递归地完成很多操作(由于函数正在处理的数据结构的约束),仅凭堆栈,我是否会遭受很多高速缓存未命中的情况?

目标是在遍历树时尝试尽可能减少高速缓存未中。

(是的,我知道。TLDR;)

在我曾经工作过的PPC(我认为是860)上,有两个缓存,即数据和代码。 (我认为它们不在CPU中,但我想它们所处的位置无关紧要。)

对于函数调用,特定的GCC编译器(您的编译器结果可能会有所不同)生成的代码

a)将函数参数压入调用函数的堆栈中(即参数加载)

接着

b)“操纵”堆栈(指针,通常是cpu寄存器)以为所有外部作用域自动变量(堆栈变量)建立空间。 (通常只需要向堆栈指针添加一个简单的字节数即可。)

注意: 输入功能/方法代码之前 ,这两个步骤均已完成。

推入函数参数将导致某些数据缓存被标记为“已触摸”(还是仍称为脏数据?),但是已触摸数据实际到达堆栈存储器的时间取决于硬件高速缓存处理程序。

函数/方法“输入”(跳转到新的pc位置)对初始化自动变量没有任何作用,这是程序员的责任。 因此,数据缓存对其不受影响。


堆栈存储器什么时候加载到CPU缓存中?

修改堆栈数据项时。 当代码将数据写入堆栈时,将涉及数据缓存。

每次函数返回?

没有。

每个递归函数调用都需要将其本地参数保存在堆栈帧中

我认为函数调用顺序更多的是, 函数开始运行之前 ,自动变量已经安装在堆栈内存位置中,位于固定编译器计算出的与堆栈指针的偏移量处。 因此,当递归(或调用任何其他函数)时,“本地”参数已经在堆栈中,因此已经“保存”。 作为函数调用(或返回)的一部分,不会有其他保存。

也许您将“函数调用”与“上下文切换”混淆了? (其中cpu寄存器也必须推出以用于ram)由于此sudo“寄存器交换”和其他os操作,功能调用的速度比上下文切换快2到3个数量级。

例如,如果我要进行很多递归函数调用,它将“破坏”我的CPU缓存吗?

不确定“破坏”缓存的含义。 (另请参阅下面的最后一段)我猜您正在考虑缓存块的大小,并可能以某种方式触发其他缓存块的写入。 而且,由于您提到了递归,也许您担心递归算法更容易出现这种情况。

各种缓存复杂性和缓存块大小意味着您唯一的方法就是进行测试。

但是,对我而言,这种担忧有一些过早的优化。 如果递归足够快,并且满足要求,那么为什么要研究它。

作为示例,我有一些代码,其中递归方法比循环实现更快(并且更具可读性)。 并且当您可以实现尾部递归时,您就不必费心手动重新编码和重新测试。 “ -O3”优化已完全消除了堆栈使用。 (易于测试。)

在遍历树时,递归地完成很多操作(由于函数正在处理的数据结构的约束),仅凭堆栈,我是否会遭受很多高速缓存未命中的情况?

就个人而言,我喜欢递归。 如果您的问题是“更轻松”地阅读和理解使用递归,则应该使用它。 我最重视可读性。 您还能如何确定正确性。


在我之前提到的嵌入式PPC上,我可以从命令行启用/禁用数据缓存和指令缓存。

我希望指令缓存能够提供良好的性能提升,但我并不失望

我对数据缓存的期望值较低,并且对它的容量感到惊讶。 我当时正在处理的代码几乎没有递归,没有树,没有大文件系统。

您可能会发现有趣的是,我的一些测量结果表明,在预加载的参数完成从数据缓存到ram的旅程之前,小型函数可以从调用中返回。 该数据缓存的等待状态为0。 就缓存而言,“参数加载”功能便宜。

堆栈可能没有完全加载到缓存中,而是仅加载了需要的缓存行。 处理器不知道哪个内存属于您的堆栈,它只会看到内存地址和高速缓存行。

因此,它不会破坏您的缓存。 很难做出准确的预测。 特别是如果您使用非重要的析构函数。

如果使用尾部调用 ,则编译器将优化代码,并且在某些情况下,递归调用完成后,堆栈将被删除-因此始终只存储一个堆栈。

明智的人说:抢先式优化是万恶之源。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM