什么是尾递归消除?

时间:2009-08-06 18:11:35

标签: language-agnostic recursion tail-recursion tail-call-optimization program-transformation

史蒂夫·叶格在blog post中提到它并且我不知道它意味着什么,有人可以填补我吗?

tail call optimization是一回事吗?

2 个答案:

答案 0 :(得分:52)

尾调用消除是一种节省堆栈空间的优化。它用转到替换功能调用。尾递归消除是一回事,但是附加了约束函数调用自身。

基本上,如果函数A执行的最后一件事是return A(params...),那么您可以消除堆栈帧的分配,而是设置适当的寄存器并直接跳转到函数体中。

考虑一个(虚构的)调用约定,该约定传递堆栈上的所有参数并在某个寄存器中返回值。

某些函数可以编译为(以虚构的汇编语言):

function:
//Reading params B, C, & D off the stack
pop B
pop C
pop D
//Do something meaningful, including a base case return
...
//Pass new values for B, C, & D to a new invocation of function on the stack
push D*
push C*
push B*
call function
ret

无论上面实际做了什么,它都会为每次调用函数占用一个全新的堆栈帧。但是,由于除了返回之外尾部调用函数之后没有任何反应,我们可以安全地优化该情况。

导致:

function:
//Reading params B, C, & D off the stack (but only on the first call)
pop B
pop C
pop D
function_tail_optimized:
//Do something meaningful, including a base case return
...
//Instead of a new stack frame, load the new values directly into the registers
load B, B*
load C, C*
load D, D*
//Don't call, instead jump directly back into the function
jump function_tail_optimized

最终结果是一个节省大量堆栈空间的等效函数,尤其是对于导致大量递归调用的输入。

我的答案中需要很多想象力,但我认为你可以理解。

答案 1 :(得分:8)

来自here

  

“...尾递归消除是一个   尾部呼叫消除的特例   尾调用是一个调用   功能本身。在那种情况下   呼叫可以被跳转到   移动后启动功能   适当的新论据   注册或堆叠位置......“

来自Wikipedia

  

“......当一个函数被调用时,计算机必须”记住“它所调用的位置,返回地址,这样一旦调用完成,它就可以返回到那个位置。通常,这个信息被保存在堆栈中,这是一个简单的返回位置列表,按照它们描述的呼叫位置的时间顺序排列。有时,函数在完成所有其他操作后所做的最后一件事就是简单地调用一个函数,可能本身使用尾递归,不需要记住我们调用的地方 - 相反,我们可以单独留下堆栈,新调用的函数将直接将结果返回给原始调用者。在这种情况下将调用转换为分支或跳转称为尾调用优化。请注意,尾部调用不必在源代码中的所有其他语句之后以词汇方式出现;它的重要性仅在于结果会立即返回,因为如果执行优化,调用函数将永远不会有机会在调用之后执行任何操作....“