gcc是否优化递归函数?怎么做?

时间:2013-04-07 14:21:09

标签: c++ c optimization gcc recursion

我今天发现了一个有趣的关于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是如何做到的?

2 个答案:

答案 0 :(得分:4)

编译器可以通过在递归函数调用之前放置乘法将该代码转换为可以优化尾部调用的东西:

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;
}

通过递归调用result*x之前执行factorial_tail_call的评估,编译器可以确定不再需要xresult。因此,它可以从堆栈中弹出它们。这表明堆栈需要增长。

你能看到转换代码之间的任何相似之处吗? 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.3p4of n1570.pdf

  

在抽象机器中,所有表达式都按照指定的方式进行评估   语义学。实际的实现不需要评估部分内容   表达式,如果它可以推断出它的值没有被使用而且没有   产生所需的副作用(包括通过调用a引起的任何副作用)   功能或访问易失性对象。)

编译器是聪明的东西,由比我们大多数人好得多的程序员编写。如果编译器可以发现两段代码是等价的,那么它可以选择它想要的两个代码中的任何一个(有一些限制,在下面的引文中描述)。例如,它可以替换一个循环,该循环使用单个printf表达式计算并打印前1000个素数。

§5.1.2.3p6of n1570.pdf

  

符合实施的最低要求是:

     

- 对volatile对象的访问严格按照   抽象机器的规则。

     

- 程序终止时,所有写入文件的数据都应该是   与根据该程序执行程序的结果相同   抽象语义会产生。

     

- 交互设备的输入和输出动态应采用   按7.21.3的规定放置。这些要求的目的是   无缓冲或行缓冲输出尽快出现   确保在程序之前实际显示提示消息   等待输入。

     

这是该计划的可观察行为。

这就是为什么微观优化是徒劳的。

如果另一个线程修改了strlen正在处理的字符串,那就是竞争条件。竞争条件是未定义的行为。您需要使用互斥锁来保护字符串以确保不会发生这种情况,或者学习更好的多线程范例。你正在读哪本书?

§5.1.2.4p25of n1570.pdf

  

如果程序包含两个,则程序的执行包含数据竞争   不同线程中的冲突操作,其中至少有一个不是   原子,并没有发生在另一个之前。任何这样的数据竞争   导致未定义的行为。

答案 1 :(得分:4)

您已经知道gcc可以将尾递归函数优化为循环。 gcc可以做的另一件事(在链接中提到)是尝试将非尾递归函数优化为尾递归函数。

您的阶乘功能在这里:

int factorial(int x) {
   if (x > 1) return x * factorial(x-1);
   else return 1;
}

现在我将尝试尽可能少地进行更改并将其重写为tail-recursive。首先,我将翻转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);
}

现在这是一个尾递归函数,它可以优化为循环。