递归,堆栈和缓存未命中
Recursion, the stack and cache misses
假设我需要遍历一棵树。据我了解,如果我以递归方式执行此操作,则每个递归函数调用都需要将其局部参数保存在堆栈帧中。栈帧驻留在栈内存中,每一帧都由一个栈指针指向。
堆栈内存何时加载到 CPU 缓存中?每次函数returns?例如,如果我正在进行大量递归函数调用,它会 "trash" 我的 CPU 缓存吗?
在遍历树时,递归地完成一些事情更容易(因为函数正在处理的数据结构的限制)我会不会因为单独的堆栈而遭受很多缓存未命中?
目标是在遍历树时尽量减少缓存未命中。
可能堆栈没有完全加载到缓存中,而是只加载了需要的缓存行。处理器不知道哪个内存属于你的堆栈,它只看到内存地址和缓存行。
因此它不会破坏您的缓存。很难做出准确的预测。特别是如果你使用非平凡的析构函数。
如果您使用 tail call,您的编译器将优化代码,并且在某些情况下,当递归调用完成时堆栈将被删除 - 因此始终只存储一个堆栈。
有智者说:抢先优化是万恶之源
(是的,是的,我知道。TLDR ;)
在我曾经工作过的一台PPC上(我记得是860),有两个
缓存,数据和代码。 (我认为他们不在 CPU,但我
suppose 他们住在哪里并不重要。)
对于函数调用,特定的 GCC 编译器(您的编译器
结果可能会有所不同)生成的代码
a) 将函数参数压入被调用函数的栈中,(即参数加载)
然后
b) 'manipulated' 堆栈(指针,通常是 cpu 寄存器)到
为所有外部范围自动建立 space
变量(堆栈变量)。 (通常只需添加一个简单的
堆栈指针的字节数。)
注意:在进入
function/method代码。
推送函数参数会导致一些数据缓存
标记为'touched'(还是还叫dirty?),但是多久
触摸的数据实际上到达堆栈内存取决于
硬件缓存处理程序。
Function/method 'entry'(跳转到新的电脑位置)确实
nothing初始化自动变量,这是
程序员的责任。因此,数据缓存不受影响
为了他们。
When does stack memory get loaded into the CPU cache?
修改堆栈数据项时。涉及数据缓存
当代码将数据写入堆栈时。
Each time the function returns?
没有。
each recursive function call would need to save its local
parameters in a stack frame
我认为函数调用顺序比自动
变量已经安装在堆栈内存位置,在一个固定的编译器计算的堆栈指针偏移处,在函数开始之前运行。因此,当递归(或调用任何其他函数)时,'local' 参数已经在堆栈中,因此已经 'saved'。作为函数调用的一部分(或 return),将不会有额外的保存。
也许您将 "function call" 与“上下文”混淆了
开关”?(其中 cpu 寄存器也必须转出到 ram)
函数调用比上下文快 2 到 3 个数量级
由于此 sudo 'register swap' 和其他 os 操作而切换。
For example, if I am doing alot of recursive function calls,
will it "trash" my CPU cache?
不确定 'trash' 缓存是什么意思。 (另请参阅我下面的最后一段)我猜您正在考虑缓存块大小并可能触发
额外的缓存块以某种方式写入。既然你提到了
递归,也许你担心递归算法会
更容易出现这种情况。
缓存复杂性和缓存块大小的多样性意味着您唯一的选择
方法是测试。
然而,对我来说,这种担忧有点过早优化的意味。
如果递归足够快,如果满足要求,为什么
你会调查一下吗
例如,我有一些递归方法的代码
比循环实现更快(以及更多
可读)。当你可以实现尾递归时,你不需要
费心手动重新编码和重新测试。 “-O3”优化有
完全删除堆栈使用。 (容易测试。)
While traversing the tree, something done alot easier
recursively (because of constraints on the data structures the
function is dealing with) will I ever suffer alot of cache
misses due to the stack alone?
就个人而言,我喜欢递归。如果您的问题是 'easier'
阅读和理解使用递归,而不是你应该使用它。我
值可读性 most。您还可以如何确定正确性。
在我之前提到的嵌入式 PPC 上,我可以从命令行启用/禁用数据缓存和指令缓存。
我希望指令缓存能够提供良好的性能 boost,
我没有失望
我对数据缓存的期望较低,并且感到非常惊讶
在幅度。我当时正在处理的代码有
很少递归,没有树,没有大文件系统。
你可能会发现我的一些测量结果很有趣
在预加载参数完成从数据缓存到 ram 的旅程之前,小函数可以从调用中 return 。
该数据缓存 运行 具有 0 个等待状态。函数 "parameter load" 是
相对于缓存便宜。
假设我需要遍历一棵树。据我了解,如果我以递归方式执行此操作,则每个递归函数调用都需要将其局部参数保存在堆栈帧中。栈帧驻留在栈内存中,每一帧都由一个栈指针指向。
堆栈内存何时加载到 CPU 缓存中?每次函数returns?例如,如果我正在进行大量递归函数调用,它会 "trash" 我的 CPU 缓存吗?
在遍历树时,递归地完成一些事情更容易(因为函数正在处理的数据结构的限制)我会不会因为单独的堆栈而遭受很多缓存未命中?
目标是在遍历树时尽量减少缓存未命中。
可能堆栈没有完全加载到缓存中,而是只加载了需要的缓存行。处理器不知道哪个内存属于你的堆栈,它只看到内存地址和缓存行。
因此它不会破坏您的缓存。很难做出准确的预测。特别是如果你使用非平凡的析构函数。
如果您使用 tail call,您的编译器将优化代码,并且在某些情况下,当递归调用完成时堆栈将被删除 - 因此始终只存储一个堆栈。
有智者说:抢先优化是万恶之源
(是的,是的,我知道。TLDR ;)
在我曾经工作过的一台PPC上(我记得是860),有两个 缓存,数据和代码。 (我认为他们不在 CPU,但我 suppose 他们住在哪里并不重要。)
对于函数调用,特定的 GCC 编译器(您的编译器 结果可能会有所不同)生成的代码
a) 将函数参数压入被调用函数的栈中,(即参数加载)
然后
b) 'manipulated' 堆栈(指针,通常是 cpu 寄存器)到 为所有外部范围自动建立 space 变量(堆栈变量)。 (通常只需添加一个简单的 堆栈指针的字节数。)
注意:在进入 function/method代码。
推送函数参数会导致一些数据缓存 标记为'touched'(还是还叫dirty?),但是多久 触摸的数据实际上到达堆栈内存取决于 硬件缓存处理程序。
Function/method 'entry'(跳转到新的电脑位置)确实 nothing初始化自动变量,这是 程序员的责任。因此,数据缓存不受影响 为了他们。
When does stack memory get loaded into the CPU cache?
修改堆栈数据项时。涉及数据缓存 当代码将数据写入堆栈时。
Each time the function returns?
没有。
each recursive function call would need to save its local parameters in a stack frame
我认为函数调用顺序比自动 变量已经安装在堆栈内存位置,在一个固定的编译器计算的堆栈指针偏移处,在函数开始之前运行。因此,当递归(或调用任何其他函数)时,'local' 参数已经在堆栈中,因此已经 'saved'。作为函数调用的一部分(或 return),将不会有额外的保存。
也许您将 "function call" 与“上下文”混淆了 开关”?(其中 cpu 寄存器也必须转出到 ram) 函数调用比上下文快 2 到 3 个数量级 由于此 sudo 'register swap' 和其他 os 操作而切换。
For example, if I am doing alot of recursive function calls, will it "trash" my CPU cache?
不确定 'trash' 缓存是什么意思。 (另请参阅我下面的最后一段)我猜您正在考虑缓存块大小并可能触发 额外的缓存块以某种方式写入。既然你提到了 递归,也许你担心递归算法会 更容易出现这种情况。
缓存复杂性和缓存块大小的多样性意味着您唯一的选择 方法是测试。
然而,对我来说,这种担忧有点过早优化的意味。 如果递归足够快,如果满足要求,为什么 你会调查一下吗
例如,我有一些递归方法的代码 比循环实现更快(以及更多 可读)。当你可以实现尾递归时,你不需要 费心手动重新编码和重新测试。 “-O3”优化有 完全删除堆栈使用。 (容易测试。)
While traversing the tree, something done alot easier recursively (because of constraints on the data structures the function is dealing with) will I ever suffer alot of cache misses due to the stack alone?
就个人而言,我喜欢递归。如果您的问题是 'easier' 阅读和理解使用递归,而不是你应该使用它。我 值可读性 most。您还可以如何确定正确性。
在我之前提到的嵌入式 PPC 上,我可以从命令行启用/禁用数据缓存和指令缓存。
我希望指令缓存能够提供良好的性能 boost, 我没有失望
我对数据缓存的期望较低,并且感到非常惊讶 在幅度。我当时正在处理的代码有 很少递归,没有树,没有大文件系统。
你可能会发现我的一些测量结果很有趣 在预加载参数完成从数据缓存到 ram 的旅程之前,小函数可以从调用中 return 。 该数据缓存 运行 具有 0 个等待状态。函数 "parameter load" 是 相对于缓存便宜。