【发布时间】:2014-09-21 20:39:44
【问题描述】:
我有一个关于保存算术计算以限制堆栈使用是否好的问题。
假设我有一个像这样的递归函数:
void foo (unsigned char x, unsigned char z) {
if (!x || !z)
return;
// Do something
for (unsigned char i = 0; i < 100; ++i) {
foo(x - 1, z);
foo(x, z - 1);
}
}
这里主要看到的是 x - 1 和 z - 1 在循环中每次评估。
为了提高性能,我会这样做:
const unsigned char minus_x = x - 1;
const unsigned char minus_z = z - 1;
for (unsigned char i = 0; i < 100; ++i) {
foo(minus_x, z);
foo(x, minus_z);
}
但是这样做意味着在每次调用时,minus_x 和 minus_z 都会保存在堆栈中。递归函数可能被调用数千次,这意味着堆栈中使用了数千个字节。此外,所涉及的数学并不像-1 那样简单。
这是个好主意吗?
编辑:它实际上没用,因为它是编译器的标准优化:Loop-invariant code motion(参见 HansPassant 的评论)
使用包含以下计算的静态数组会更好:
static const char minuses[256] = {/* 0 for x = 0; x - 1 for x = 1 to 255 */}
然后做:
foo(minuses[x], z);
foo(x, minuses[z]);
这种方法极大地限制了所需的实际数学运算,但在每次调用时,它必须获取数组中的单元格,而不是从寄存器中读取它。
我正在尝试尽可能多地进行基准测试以找到最佳解决方案,但如果有最佳实践或我在这里遗漏了什么,请告诉我。
【问题讨论】:
-
操作
x - 1无论如何都会在堆栈上创建一个值(即使您没有将它存储在局部变量中)。 -
@barakmanos 当然,但是对于局部变量,您在保存局部变量期间进行一次推送,在复制下一个函数调用的参数时进行一次推送。至少我是这么认为的。
-
您可以简单地尝试这两个选项,运行到深度 2 或 3,然后检查 SP 寄存器的值。
-
@barakmanos 现在就做。
-
您是否看到此代码中的实际性能受到影响?现代编译器足够聪明,可以自动优化这些条件。如果有真正的问题,您可以比较生成的机器代码。
标签: c performance recursion stack