Optimization 部分尾部递归函数是否仍能获得完全尾部递归函数的优化优势? 我意识到,对于不同的语言,这个问题的答案可能是不同的,我最感兴趣的语言是C++。如果因为无法以语言无关的方式回答而需要更改标记,请随意。 有没有可能让一个函数部分是尾部递归的,并且仍然可以获得尾部递归的好处
据我所知,尾部递归是这样的:编译器将优化函数,而不是执行完整的函数调用,从而将参数更改为新参数,并跳到函数的开头 如果您有这样一个函数:Optimization 部分尾部递归函数是否仍能获得完全尾部递归函数的优化优势? 我意识到,对于不同的语言,这个问题的答案可能是不同的,我最感兴趣的语言是C++。如果因为无法以语言无关的方式回答而需要更改标记,请随意。 有没有可能让一个函数部分是尾部递归的,并且仍然可以获得尾部递归的好处,optimization,language-agnostic,partial,tail-recursion,Optimization,Language Agnostic,Partial,Tail Recursion,据我所知,尾部递归是这样的:编译器将优化函数,而不是执行完整的函数调用,从而将参数更改为新参数,并跳到函数的开头 如果您有这样一个函数: def example(arg): if arg == 0: return 0 # base case if arg % 2 == 0: return example(arg - 1) # should be tail recursive return 3 + example(arg - 1) # i
def example(arg):
if arg == 0:
return 0 # base case
if arg % 2 == 0:
return example(arg - 1) # should be tail recursive
return 3 + example(arg - 1) # isn't tail recursive because 3 is added to the result
当一个乐观主义者遇到类似的情况时(函数在某些情况下是尾部递归的,而在其他情况下不是),它会把一个变成
跳转
,另一个变成调用
,还是会把一些优化现实的事实(如果我知道的话,我不会问)使它不得不将所有内容都转换为调用
,并失去如果函数是尾部递归的,您将拥有的所有效率?据我所知,智能编译器可以通过跳到示例入口点而不是设置新的堆栈框架,将尾部递归应用于您的第一次调用。下面的返回将向原始调用方展开堆栈,在一个步骤中有效地“结束”两个调用,即使它不能对另一个调用这样做
您可以通过在调用中添加3来优化函数:
def example(arg, add=0):
arg += add
....
return example(arg - 1, 3) # tail now too
另一种技术是创建第二个函数,并让两者相互调用
我不知道Python或C++编译器是否可以处理,但是可以检查C++的程序集输出。奇怪的是,我认为检查python的字节码输出可能更难。
据我所知,智能编译器可以通过跳转到示例入口点而不是设置新的堆栈框架,将尾部递归应用于第一次调用。下面的返回将向原始调用方展开堆栈,在一个步骤中有效地“结束”两个调用,即使它不能对另一个调用这样做 您可以通过在调用中添加3来优化函数:def example(arg, add=0):
arg += add
....
return example(arg - 1, 3) # tail now too
另一种技术是创建第二个函数,并让两者相互调用
我不知道Python或C++编译器是否可以处理,但是可以检查C++的程序集输出。奇怪的是,我认为检查python的字节码输出可能更难。
在Scheme中,我想到尾部调用时想到的第一种语言,第二种情况是语言规范保证的尾部调用。(术语说明:最好将此类函数调用称为“尾部调用”。) Scheme规范准确地定义了Scheme中的尾部调用,并要求编译器专门支持它们。您可以在11.20中看到定义。R6RS()的尾部调用和尾部上下文 请注意,在Scheme中,规范没有提到尾部调用的优化。相反,它说一个实现必须支持无限数量的活动尾部调用——这是语言运行时的语义属性。它们可以实现为普通调用,但通常不是 示例,用C: 以您的示例的C版本为例int example(int arg)
{
if (arg == 0)
return 0;
if ((arg % 2) == 0)
return example(arg - 1);
return 3 + example(arg - 1);
}
使用gcc针对i386的常用优化设置(-O2
)编译它:
_example:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
movl 8(%ebp), %edx
testl %edx, %edx
jne L5
jmp L15
.align 4,0x90
L14:
decl %edx
testl %edx, %edx
je L7
L5:
testb $1, %dl
je L14
decl %edx
addl $3, %eax
testl %edx, %edx
jne L5
L7:
leave
ret
L15:
leave
xorl %eax, %eax
ret
请注意,汇编代码中没有函数调用。GCC不仅将尾部调用优化为跳转,还将非尾部调用优化为跳转。在Scheme(我想到尾部调用时想到的第一种语言)中,语言规范保证第二种情况是尾部调用。(术语说明:最好将此类函数调用称为“尾部调用”。) Scheme规范准确地定义了Scheme中的尾部调用,并要求编译器专门支持它们。您可以在11.20中看到定义。R6RS()的尾部调用和尾部上下文 请注意,在Scheme中,规范没有提到尾部调用的优化。相反,它说一个实现必须支持无限数量的活动尾部调用——这是语言运行时的语义属性。它们可以实现为普通调用,但通常不是 示例,用C: 以您的示例的C版本为例
int example(int arg)
{
if (arg == 0)
return 0;
if ((arg % 2) == 0)
return example(arg - 1);
return 3 + example(arg - 1);
}
使用gcc针对i386的常用优化设置(-O2
)编译它:
_example:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
movl 8(%ebp), %edx
testl %edx, %edx
jne L5
jmp L15
.align 4,0x90
L14:
decl %edx
testl %edx, %edx
je L7
L5:
testb $1, %dl
je L14
decl %edx
addl $3, %eax
testl %edx, %edx
jne L5
L7:
leave
ret
L15:
leave
xorl %eax, %eax
ret
请注意,汇编代码中没有函数调用。GCC不仅将尾部调用优化为跳转,还将非尾部调用优化为跳转。是的,我知道我可以在无条件尾部递归的地方更改它,但我想给出一个我所说的示例。是的,我非常确信C++(至少MSVC++)可以将相互递归的函数转换为TAU-CAL。CPYTHON实现从来没有优化尾部调用,Guido已经开始讨论这个主题,称之为“unPythic”。看@Dietrich是的,我在尝试应该是尾部递归阶乘函数时注意到了这一点:)太糟糕了,guido太短视了。@Seth:请不要如此粗鲁地谈论语言设计师。尾部调用优化需要权衡,这不是一个明显的选择。是的,我知道我可以在无条件尾部递归的地方改变它,但我想给出一个我所说的例子。是的,我非常确信C++(至少MSVC++)可以将相互递归的函数转换为TAU-CAL。CPYTHON实现从来没有优化尾部调用,Guido已经开始讨论这个主题,称之为“unPythic”。看@Dietrich是的,我在尝试应该是尾部递归阶乘函数时注意到了这一点:)太糟糕了,guido太短视了。@Seth:请不要如此粗鲁地谈论语言设计师。助教