递归、堆栈和缓存未命中

Recursion, the stack and cache misses

本文关键字:缓存 堆栈 递归      更新时间:2023-10-16

想象一下我需要遍历一棵树。据我所知,如果我以递归的方式进行,每个递归函数调用都需要将其本地参数保存在堆栈帧中。堆栈帧位于堆栈内存中,每个帧都由堆栈指针指向。

堆栈内存何时加载到CPU缓存中?每次函数返回时?例如,如果我正在进行大量递归函数调用,它会"丢弃"我的CPU缓存吗?

当遍历树时,递归地做一些容易得多的事情(因为函数处理的数据结构受到限制),我会因为堆栈而遭受很多缓存未命中吗?

目标是在遍历树时尽量减少缓存未命中。

(是的,是的,我知道.TLDR;)

在我曾经工作过的PPC上(我想是860),有两个缓存、数据和代码。(我想它们不在中央处理器里,但我假设他们住在哪里并不重要。)

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

a) 将函数参数推送到被调用函数的堆栈上(即参数加载)

然后

b) "操纵"堆栈(指针,通常是cpu寄存器)为所有外部范围自动建立空间变量(堆栈变量)。(通常只需添加一个堆栈指针的字节计数。)

注意:这两个步骤都是在进入函数/方法代码。

推送函数参数会导致一些数据缓存标记为"触摸"(或者它仍然被称为肮脏?),但多久实际到达堆栈内存的触摸数据取决于hw缓存处理程序。

函数/方法"entry"(跳转到新的pc位置)nothing来初始化自动变量,这是程序员的责任。因此,数据缓存不受影响为他们。


堆栈内存何时加载到CPU缓存中?

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

每次函数返回时?

没有。

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

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

也许您混淆了"函数调用"answers"上下文"开关"(其中cpu寄存器也必须扩展到ram)函数调用比上下文快2到3个数量级切换,因为这个sudo"寄存器交换"和其他操作系统操作。

例如,如果我正在进行大量递归函数调用,它会"丢弃"我的CPU缓存吗?

不知道你说的"丢弃"缓存是什么意思。(另请参阅我下面的最后一段)我猜您正在考虑缓存块大小和潜在的触发以某种方式写入额外的缓存块。既然你提到递归,也许你担心递归算法会更容易发生这种事。

缓存复杂性和缓存块大小的多样性意味着方法是测试。

然而,对我来说,这样的担忧有点早熟优化的味道。如果递归足够快,如果它满足要求,为什么你能查一下吗?

举个例子,我有一些代码,其中递归方法比循环实现更快(以及可读)。当您可以实现尾部递归时,您不需要麻烦手动重新编码并重新测试。"-O3"优化完全去除了堆叠使用。(易于测试。)

在遍历树时,做一些事情要容易得多递归地(由于数据结构的约束函数正在处理)我会遭受大量缓存吗仅因堆栈而未命中?

就我个人而言,我喜欢递归。如果你的问题"更容易"阅读和理解使用递归,而不是你应该使用它最重视可读性。否则你将如何确定正确性。


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

我期望指令缓存能够提供良好的性能提升,我并没有对感到失望

我对数据缓存的期望值较低,对此感到非常惊讶在量级上。我当时正在编写的代码很少递归,没有树,没有大文件系统。

你可能会觉得有趣的是,我的一些测量结果显示在预加载的参数完成从数据缓存到ram的过程之前,小函数可以从调用中返回。该数据缓存在0个等待状态下运行。函数"参数加载"在缓存方面便宜。

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

所以它不会破坏你的缓存。很难做出准确的预测。特别是如果你使用非三重析构函数。

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

有智者说:抢先优化是万恶之源。