gcc优化递归函数吗?怎么做
Does gcc optimize recursive functions? How to do it?
今天我发现了一个关于gcc的有趣测试http://ridiculousfish.com/blog/posts/will-it-optimize.html
这段代码是怎么来的
int factorial(int x) {
if (x > 1) return x * factorial(x-1);
else return 1;
}
可以被编译器翻译成
int factorial(int x) {
int result = 1;
while (x > 1) result *= x--;
return result;
}
这是真的吗?gcc是如何做到的?
您已经知道,gcc可以将尾递归函数优化为循环。gcc可以做的另一件事(在您的链接中提到)是尝试将非尾部递归函数优化为尾部递归函数。
你的阶乘函数在这里:
int factorial(int x) {
if (x > 1) return x * factorial(x-1);
else return 1;
}
现在我将尝试做尽可能少的更改,并将其重写为尾部递归。首先,我将翻转if测试:
int factorial(int x) {
if (!(x > 1)) return 1;
else return x * factorial(x-1);
}
接下来,我将删除不需要的else
:
int factorial(int x) {
if (!(x > 1)) return 1;
return x * factorial(x-1);
}
这几乎是尾部递归的,但它返回的是x * factorial()
而不仅仅是factorial()
。使尾部递归的典型方法是包含第二个参数,它是一个累加器。
int factorial(int x, int accumulator = 1) {
if (!(x > 1)) return accumulator;
return factorial(x - 1, x * accumulator);
}
现在这是一个尾递归函数,它可以被优化成一个循环。
编译器可以通过将乘法放在递归函数调用之前将该代码转换为尾部调用可优化的代码:
int factorial(int x) {
return factorial_tail_call(x, 1);
}
int factorial_tail_call(int x, int result) {
if (x > 1) return factorial_tail_call(x-1, result*x);
return result;
}
通过在递归调用factorial_tail_call
之前执行result*x
的求值,编译器可以确定不再需要x
和result
。因此,它可以从堆栈中弹出它们。这就证明了堆栈不需要增长。
你能看出转换后的代码之间有什么相似之处吗?1
在同一个地方,条件x > 1
在同一个地方,return result;
在同一个地方。这只是表达相同算法的不同方式,提供了编译器实现尾部调用优化。通过将乘法表达式移动到一个参数中,并在帖子右侧的代码中放入注释,您可能能够看到功能的一些相似之处,以及编译器如何设法完成转换的其余部分:
int factorial(int x) {
return factorial_tail_call(x, 1); // int result = 1;
}
int factorial_tail_call(int x, int result) {
if (x > 1) return factorial_tail_call(x-1, result*x); // while (x > 1) result *= x--;
return result; // return result;
}
§5.1.2.3p4 of n1570.pdf
在抽象机器中,所有表达式都按照的语义。实际的实现不需要计算An的一部分表达式,如果它可以推断出它的值没有被使用,并且没有产生所需的副作用(包括调用函数或访问易失性对象)。
编译器是聪明的东西,由比我们大多数人优秀得多的程序员编写。如果编译器可以找出两段代码是等价的,那么它可以选择它所希望的两段代码中的任何一段(有一些限制,在下面的引语中描述)。例如,它可以用单个printf
表达式替换计算并打印前1000个素数的循环。
§5.1.2.3p6 of n1570.pdf
一致性实现的最低要求是:
-对易失性对象的访问严格按照抽象机器的规则。
-在程序终止时,所有写入文件的数据都应该是与执行程序的结果相同抽象语义会产生。
-交互设备的输入和输出动态应采取按7.21.3规定放置。这些要求的目的是未缓冲或行缓冲的输出尽可能快地出现确保提示消息实际出现在程序之前等待输入
这是程序的可观察行为。
这就是为什么微优化是徒劳的原因之一。
如果另一个线程修改了strlen正在处理的字符串,这是一个竞争条件。竞态条件是未定义的行为。您需要使用互斥锁来保护字符串,以确保不会发生这种情况,或者学习更好的多线程范例。你在读哪本书?
§5.1.2.4p25 of n1570.pdf
如果程序包含两个数据竞争,则该程序的执行包含一个数据竞争不同线程中的冲突操作,其中至少有一个不是原子的,两者都不发生在对方之前。任何这样的数据竞赛导致未定义的行为。
- 递归函数计算序列中的平方和(并输出过程)
- 如何在Elixir中调用递归函数并行
- 递归函数有效,但无法记忆
- 为什么我的递归函数按降序打印,然后按升序打印?
- 为什么递归函数的最终输出是 5?
- 有没有办法使用递归函数找到数组中最小值的 INDEX?C++
- 如何将记忆应用于此递归函数?
- 如何从递归函数中完全返回,该函数给出了每个函数结果的累积相加?
- 无穷大而循环时具有递归函数
- 即使没有调用这个递归函数,它是如何工作的?
- 如何使此递归函数从给定的起始位置返回最小的整数?
- 尾递归函数未被 g++ 优化
- 优化 BigInt 递归函数
- 优化递归函数
- 优化此递归函数[Boggle resolver]
- 在turbo c++中,可以将一个普通递归函数转换为尾递归函数来对其进行优化
- 为什么当递归函数结果相乘时,g++ 仍然优化尾递归
- 尝试优化和理解打印数字除数的递归函数的运行时
- 优化递归函数
- gcc优化递归函数吗?怎么做