想象一下,我需要遍历一棵树。据我了解,如果我以递归方式进行,每个递归函数调用都需要将其本地参数保存在堆栈帧中。堆栈帧驻留在堆栈存储器中,每个帧由堆栈指针指向。
什么时候堆栈内存加载到CPU缓存中?每次函数返回?例如,如果我正在进行大量的递归函数调用,它会“废弃”我的CPU缓存吗?
在遍历树时,一些事情很容易递归(由于函数处理的数据结构的约束),我会因为单独的堆栈而遭受很多缓存未命中吗?
目标是在遍历树时尽量减少缓存未命中。
答案 0 :(得分:3)
(是的,是的,我知道.TLDR;)
在我曾经工作的PPC上(我认为是860),有两个 缓存,数据和代码。 (我认为他们不在CPU中,但我 假设他们居住的地方并不重要。)
对于函数调用,那个特定的GCC编译器(你的编译器 结果可能会有所不同)生成的代码
a)将函数参数推送到被调用函数的堆栈(即参数加载)
然后
b)'操纵'堆栈(指针,通常是cpu寄存器)来 为所有外部范围自动建立空间 变量(堆栈变量)。 (通常只需添加一个简单的 字节计数到堆栈指针。)
注意:在进入之前完成了这两个步骤 功能/方法代码。
推送函数参数将导致一些数据缓存 标记为'触摸'(或者它仍然被称为脏?),但多久 触及的数据实际上到达堆栈内存是由 hw缓存处理程序。
函数/方法'entry'(跳转到新的pc位置) nothing 初始化自动变量,这就是 程序员的责任。因此,数据缓存不受影响 对他们来说。
什么时候堆栈内存被加载到CPU缓存中?
修改堆栈数据项时。涉及数据缓存 当代码将数据写入堆栈时。
每次函数返回?
没有。
每个递归函数调用都需要保存其本地 堆栈框架中的参数
我认为函数调用序列更多的是自动调用 变量已经安装在堆栈内存位置,在堆栈指针的固定编译器计算偏移处,在函数开始运行之前。因此,当递归(或调用任何其他函数)时,'local'参数已经在堆栈中,因此已经“保存”了。作为函数调用(或返回)的一部分,不会有额外的保存。
也许你将“函数调用”与“上下文”混为一谈 切换“?(其中cpu寄存器也必须推出到ram) 函数调用比上下文快2到3个数量级 因为这个sudo'register swap'和其他os动作而切换。
例如,如果我正在做很多递归函数调用, 它会“废弃”我的CPU缓存吗?
不确定“垃圾”缓存是什么意思。 (另请参阅下面的最后一段)我猜您正在考虑缓存块大小和可能触发的问题 额外的缓存块以某种方式写入。既然你提到了 递归,也许你担心递归算法会 更容易发生这样的事情。
各种缓存复杂性和缓存块大小意味着您唯一的 方法是测试。
但是,对我来说,这种担忧会引发前期优化。 如果递归足够快,如果它符合要求,为什么 你会调查它吗?
作为一个例子,我有一些递归方法的代码 比循环实现更快(以及更多 可读的)。当你可以实现尾递归时,你不需要 懒得手动重新编码和重新测试。 “-O3”优化了 完全删除了堆栈使用。 (轻松测试。)
在遍历树时,做了很多事情 递归地(因为对数据结构的约束) 功能正在处理)我将遭受很多缓存 单独堆叠会导致错过吗?
就个人而言,我喜欢递归。如果您的问题“更容易” 阅读和理解使用递归,而不是你应该使用它。一世 价值可读性最多。你怎么能确定正确性呢。
在我之前提到的嵌入式PPC上,我可以从命令行启用/禁用数据缓存和指令缓存。
我希望指令缓存能够提供良好的性能提升, 而且我没有失望
我对数据缓存的期望值较低,并且非常惊讶 在数量上。我当时正在处理的代码有 小递归,没有树,没有大文件系统。
您可能会发现我的一些测量显示有趣 在预加载的参数将从数据缓存输出到ram之前,小函数可以从调用返回。 该数据缓存以0等待状态运行。功能“参数加载”是 关于缓存便宜。
答案 1 :(得分:2)
可能堆栈没有完全加载到缓存中,而是仅加载所需的缓存行。处理器不知道哪个内存属于您的堆栈,它只看到内存地址和缓存行。
所以它不会丢弃你的缓存。准确的预测很难做到。特别是如果你使用非trival析构函数。
如果使用tail call,编译器将优化代码,在某些情况下,在递归调用完成时删除堆栈 - 因此始终只存储一个堆栈。
有些聪明人说:先发优化是万恶之源。