在Erlang中使用大量的尾递归会减慢它吗?

时间:2009-07-09 18:21:36

标签: erlang tail-recursion

由于难以使用迭代循环,我最近一直在阅读关于Erlang的内容以及如何大量使用尾递归。

这种递归的高使用是否会降低它的速度,所有函数调用以及它们对堆栈的影响是什么?或者尾递归否定了大部分内容?

5 个答案:

答案 0 :(得分:17)

重点是Erlang优化尾调用(不仅是递归)。优化尾调用非常简单:如果返回值是通过调用另一个函数计算的,那么另一个函数不仅仅放在调用函数顶部的函数调用堆栈上,而是当前函数的堆栈帧是被其中一个被调用函数替换。这意味着尾调用不会增加堆栈大小。

所以,不,使用尾递归不会减慢Erlang的速度,也不会造成堆栈溢出的风险。

使用尾调用优化,你不仅可以使用简单的尾递归,还可以使用几个函数的相互尾递归(尾调用b,尾调用c,尾调用...)。这有时可能是一个很好的计算模型。

答案 1 :(得分:8)

迭代尾递归通常使用Tail calls实现。 这基本上是递归调用到简单循环的转换。

C#示例:

uint FactorialAccum(uint n, uint accum) {
    if(n < 2) return accum;
    return FactorialAccum(n - 1, n * accum);
};
uint Factorial(uint n) {
    return FactorialAccum(n, 1);
};

uint FactorialAccum(uint n, uint accum) {
start:
    if(n < 2) return accum;
    accum *= n;
    n -= 1;
goto start;
};
uint Factorial(uint n) {
    return FactorialAccum(n, 1);
};

甚至更好:

uint Factorial(uint n) {
    uint accum = 1;
start:
    if(n < 2) return accum;
    accum *= n;
    n -= 1;
goto start;
};

C#不是真正的尾递归,这是因为返回值被修改,大多数编译器都不会将其分解为循环:

int Power(int number, uint power) {
    if(power == 0) return 1;
    if(power == 1) return number;
    return number * Power(number, --power);
}

int Power(int number, uint power) {
    int result = number;
start:
    if(power == 0) return 1;
    if(power == 1) return number;
    result *= number;
    power--;
goto start;
}

答案 2 :(得分:3)

在大多数情况下,它不应影响性能。您正在寻找的不仅仅是尾调用,还有尾调用优化(或尾调用消除)。尾调用优化是一种编译器或运行时技术,可以确定对函数的调用是否相当于“弹出堆栈”以返回正确的函数而不是仅返回。通常尾部调用优化只能在递归调用是函数中的最后一个操作时完成,所以你必须要小心。

答案 3 :(得分:2)

尾部递归存在一个问题,但它与性能无关 - Erlang尾部递归优化还涉及消除堆栈跟踪以进行调试。

例如,请参阅Erlang FAQ的第9.13点:

Why doesn't the stack backtrace show the right functions for this code:

        -module(erl).
        -export([a/0]).

        a() -> b().
        b() -> c().
        c() -> 3 = 4.           %% will cause badmatch

The stack backtrace only shows function c(), rather than a(), b() and c().
This is because of last-call-optimisation; the compiler knows it does not need
to generate a stack frame for a() or b() because the last thing it did was
call another function, hence the stack frame does not appear in the stack
backtrace.

当你遇到崩溃时,这可能会有点痛苦(但它确实与函数式编程有关......)

答案 4 :(得分:1)

将程序文本函数调用与实现函数调用分开的类似优化是“内联”。在现代/周到语言中,函数调用与机器级函数调用几乎没有关系。