Ruby是否执行尾调用优化?
Ruby是否执行尾调用优化?
尾调用优化是一种优化技术,可以在函数调用的尾部发生时,将其转化为跳转而不是真正的函数调用。这可以减少函数调用的开销,特别是在递归函数中。然而,在Ruby中,尾调用优化并不是默认开启的。
Ruby MRI(1.9、2.0和2.1)可以通过以下方式开启尾调用优化:
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
在Ruby 2.0中,有一个提案将尾调用优化默认开启。这个提案还解释了一些相关问题。从提案中摘录的一段话如下:
“通常,尾递归优化包括另一种优化技术 - 将‘调用’转化为‘跳转’。在我看来,这种优化很难实现,因为在Ruby的世界中很难识别‘递归’。
下面是一个例子。在‘else’子句中,fact()方法的调用就不是‘尾调用’。”
def fact(n)
if n < 2
1
else
n * fact(n-1)
end
end
如果想要在fact()方法中使用尾调用优化,可以按照以下方式修改fact()方法(使用延续传递风格):
def fact(n, r)
if n < 2
r
else
fact(n-1, n*r)
end
end
总结起来,Ruby并不默认执行尾调用优化。然而,可以通过设置来开启尾调用优化。尾调用优化可以减少函数调用的开销,特别是在递归函数中。尽管识别递归在Ruby中可能有一些困难,但通过修改函数的编写风格,可以实现尾调用优化。
Ruby不支持尾递归优化(Tail Call Optimization, TCO)。虽然Ruby语言规范没有明确说明关于TCO的问题,但也没有禁止实现TCO。所以你不能依赖于TCO。
与Scheme不同,Scheme的语言规范要求所有实现都必须支持TCO。但与之相反,Guido van Rossum多次明确表示Python不应该支持TCO。
Yukihiro Matsumoto对TCO持有同情态度,但他不希望强制所有实现都支持它。不幸的是,这意味着你不能依赖于TCO,或者如果你依赖于它,你的代码将无法在其他Ruby实现中运行。
因此,一些Ruby实现支持TCO,但大多数不支持。例如,YARV(Yet Another Ruby VM)支持TCO,尽管(目前)你需要在源代码中显式取消注释一行并重新编译VM以激活TCO - 在未来的版本中,TCO将默认开启,当实现证明稳定后。Parrot虚拟机原生支持TCO,因此Cardinal也很容易支持它。CLR对TCO有一定的支持,这意味着IronRuby和Ruby.NET可能也可以实现TCO。Rubinius也可能实现TCO。
但JRuby和XRuby不支持TCO,除非JVM本身支持TCO。问题是,如果你想要一个快速的实现,并且与Java的集成快速无缝,那么你应该与Java具有堆栈兼容性,并尽可能多地使用JVM的堆栈。你可以很容易地使用跳板或显式的传递风格实现TCO,但这样你就不再使用JVM堆栈,这意味着每次你想要调用Java或从Java调用Ruby时,都必须执行某种转换,这是很慢的。因此,XRuby和JRuby选择了速度和Java集成,而不是TCO和continuations(基本上有相同的问题)。
这适用于所有希望与不支持TCO的某些宿主平台紧密集成的Ruby实现。例如,我猜MacRuby也会遇到同样的问题。
我可能是错误的(如果是的话,请告诉我),但我怀疑TCO在真正的面向对象语言中没有任何意义,因为尾调用必须能够重用调用者的堆栈帧。由于在运行时不知道通过消息发送将调用哪个方法,因此很难确保这一点(也许通过类型反馈JIT,或者通过强制所有消息的实现者使用相同大小的堆栈帧,或者通过将TCO限制为相同消息的自我发送…)。
这是一个很好的回答。通过谷歌很难找到这些信息。有趣的是,yarv支持它。
Damien,事实证明,TCO实际上对于真正的面向对象语言是必需的:请参见projectfortress.sun.com/Projects/Community/blog/…。不要太担心堆栈帧的问题:可以很好地设计堆栈帧,使其与TCO良好地配合工作。
tonyg从灭绝中保存了GLS引用的帖子,将其镜像在这里:eighty-twenty.org/index.cgi/tech/oo-tail-calls-20111001.html
我正在做一个要求我反汇编任意深度的嵌套数组的作业。显而易见的方法是使用递归,而且在线上找到的类似用例(我能找到的)也使用递归。虽然即使没有TCO,我的特定问题很不可能出现问题,但是我不能写一个完全通用的解决方案而不转换为迭代,这让我感到困扰。
我似乎找不到TCO的任何缺点,为什么Python要强制不实现它?
stackoverflow.com/questions/13591970/… 简而言之:作者的观点,奇怪的回溯,对实现细节的不希望依赖。虽然我不同意这个观点,但这些是原因。
你不是重用堆栈帧,你只是移除旧的堆栈帧,因为你已经用完了。
哇,又一个原因证明Matz是最好的仁慈的独裁者。哥们,你再也找不到比他更好的了!
如果有人对Frank和Damien提到的文章感兴趣,这是一个新的链接,可以打开。这篇文章非常有趣,所以我强烈推荐!eighty-twenty.org/2011/10/01/oo-tail-calls