Ruby是否执行尾巴呼叫优化?

函数式语言导致使用recursion来解决很多问题,因此其中许多执行尾巴呼叫优化(TCO)。 TCO导致从另一个函数(或其本身,在这种情况下该函数也被称为TCOrecursion消除,这是TCO的一个子集)调用函数,作为该函数的最后一步,不需要新的堆栈帧,这减less了开销和内存使用。

Ruby显然已经从function语言(lambdas,像map等等的函数)中“借用了”一些概念,这让我很好奇:Ruby是否执行尾部调用优化?

不,Ruby不执行TCO。 但是,它也不会执行TCO。

Ruby语言规范并没有说TCO。 这并不是说你必须这样做,但也不是说你不能这样做。 你不能依靠它。

这与Scheme不同,Scheme语言规范要求 所有实现必须执行TCO。 但是它也不像Python,Guido van Rossum在多个场合(前几天最后一次)都明确表示Python实现不应该执行TCO。

松本幸弘对TCO表示同情,他不想强迫所有的实施支持。 不幸的是,这意味着您不能依赖TCO,否则您的代码将不能再移植到其他Ruby实现中。

所以,一些Ruby实现会执行TCO,但大多数不会。 例如,YARV支持TCO,尽pipe(目前)您必须明确取消注释源代码中的行并重新编译VM,以激活TCO – 在未来的版本中,默认情况下会在执行后稳定。 Parrot虚拟机原生支持TCO,因此Cardinal也可以很容易地支持它。 CLR对TCO有一些支持,这意味着IronRuby和Ruby.NET可能会这样做。 Rubinius也可以做到这一点。

但JRuby和XRuby不支持TCO,除非JVM本身获得对TCO的支持,否则它们可能不会。 问题是这样的:如果你想快速实现,并与Java快速无缝集成,那么你应该与Java堆栈兼容,尽可能使用JVM堆栈。 你可以很容易地用蹦床或明确的继续传递风格实现TCO,但是你不再使用JVM栈,这意味着每次你想调用Java或者从Java调用到Ruby,你都必须执行某种转换,这是缓慢的。 所以,XRuby和JRubyselect在TCO和延续(基本上都有相同的问题)上进行速度和Java整合。

这适用于所有想要与本地不支持TCO的主机平台紧密集成的Ruby的所有实现。 例如,我猜MacRuby会有同样的问题。

更新:这里是对Ruby的TCO很好的解释: http : //nithinbekal.com/posts/ruby-tco/

更新:你可能也想看看tco_method gem: http : //blog.tdg5.com/introducing-the-tco_method-gem/

在Ruby MRI(1.9,2.0和2.1)中,您可以通过以下方式启用TCO:

RubyVM::InstructionSequence.compile_option = { :tailcall_optimization => true, :trace_instruction => false } 

有一个build议是在Ruby 2.0中默认打开TCO。 这也解释了一些问题: 尾巴呼叫优化:默认启用?

摘自链接:

通常,尾recursion优化包括另一种优化技术 – “呼叫”“跳转”翻译。 在我看来,应用这种优化是很困难的,因为在Ruby的世界里认识到“recursion”是很困难的。

下一个例子。 “else”子句中的fact()方法调用不是“尾部调用”。

 def fact(n) if n < 2 1 else n * fact(n-1) end end 

如果要对fact()方法使用tail-call优化,则需要按如下所示更改fact()方法(continuation passing style)。

 def fact(n, r) if n < 2 r else fact(n-1, n*r) end end 

它可以但不能保证:

https://bugs.ruby-lang.org/issues/1256

在编译之前,可以通过在vm_opts.h中调整几个variables来编译TCO: https : //github.com/ruby/ruby/blob/trunk/vm_opts.h#L21

 // vm_opts.h #define OPT_TRACE_INSTRUCTION 0 // default 1 #define OPT_TAILCALL_OPTIMIZATION 1 // default 0 

这build立在Jörg和Ernest的答案上。 基本上取决于实施。

我不能得到欧内斯特的核磁共振成像的答案,但它是可行的。 我发现这个例子适用于MRI 1.9到2.1。 这应该打印一个非常大的数字。 如果您没有将TCO选项设置为true,则应该得到“堆栈太深”的错误。

 source = <<-SOURCE def fact n, acc = 1 if n.zero? acc else fact n - 1, acc * n end end fact 10000 SOURCE i_seq = RubyVM::InstructionSequence.new source, nil, nil, nil, tailcall_optimization: true, trace_instruction: false #puts i_seq.disasm begin value = i_seq.eval p value rescue SystemStackError => e pe end