【问题标题】:Does Ruby perform Tail Call Optimization?Ruby 是否执行尾调用优化?
【发布时间】:2010-10-23 21:34:34
【问题描述】:

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

Ruby 显然从函数式语言中“借用”了一些概念(lambda、map 等函数等),这让我很好奇:Ruby 是否执行尾调用优化?

【问题讨论】:

    标签: ruby functional-programming tail-recursion


    【解决方案1】:

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

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

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

    Yukihiro Matsumoto 很同情 TCO,他只是不想强迫 所有 实现支持它。不幸的是,这意味着您不能依赖 TCO,否则,您的代码将无法再移植到其他 Ruby 实现。

    因此,一些 Ruby 实现执行 TCO,但大多数不执行。例如,YARV 支持 TCO,尽管(目前)您必须明确取消注释源代码中的一行并重新编译 VM,以激活 TCO——在未来的版本中,它将默认启用,在实现证明之后稳定的。 Parrot 虚拟机本身就支持 TCO,因此 Cardinal 也可以很容易地支持它。 CLR 对 TCO 有一些支持,这意味着 IronRuby 和 Ruby.NET 可能会做到这一点。鲁比尼乌斯或许也能做到。

    但是 JRuby 和 XRuby 不支持 TCO,而且他们可能不会,除非 JVM 本身获得对 TCO 的支持。问题是这样的:如果你想有一个快速的实现,和Java的快速无缝集成,那么你应该与Java堆栈兼容,并尽可能使用JVM的堆栈。您可以很容易地使用蹦床或显式延续传递样式实现 TCO,但是您不再使用 JVM 堆栈,这意味着每次您想调用 Java 或从 Java 调用 Ruby 时,都必须执行某种转换,很慢。因此,XRuby 和 JRuby 选择了速度和 Java 集成,而不是 TCO 和延续(基本上有同样的问题)。

    这适用于希望与某些不支持本机 TCO 的主机平台紧密集成的所有 Ruby 实现。例如,我猜 MacRuby 也会遇到同样的问题。

    【讨论】:

    • 我可能弄错了(如果是,请赐教),但我怀疑 TCO 在真正的 OO 语言中是否有意义,因为尾调用必须能够重用调用者堆栈帧。由于使用后期绑定,在编译时不知道消息发送会调用哪个方法,因此似乎很难确保(可能使用类型反馈 JIT,或者通过强制消息的所有实现者使用堆栈帧大小相同,或将 TCO 限制为自发送相同的消息……)。
    • 这是一个很好的回应。通过谷歌不容易找到该信息。有趣的是 yarv 支持它。
    • Damien,事实证明,对于真正的 OO 语言,TCO 实际上是必需的:请参阅projectfortress.sun.com/Projects/Community/blog/…。不要太担心堆栈帧的问题:完全可以合理地设计堆栈帧,以便它们与 TCO 很好地配合。
    • tonyg 将 GLS 引用的帖子从灭绝中保存下来,在此处进行镜像:eighty-twenty.org/index.cgi/tech/oo-tail-calls-20111001.html
    • 我正在做一个homework assignment,它要求我反汇编一组任意深度的嵌套数组。明显的方法是递归,在线类似的用例(我可以找到)使用递归。即使没有 TCO,我的特定问题也不太可能爆发,但如果不切换到迭代,我就无法编写一个完全通用的解决方案,这让我很困扰。
    【解决方案2】:

    更新:以下是对 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
    }
    

    有一个提议在 Ruby 2.0 中默认开启 TCO。它还解释了随之而来的一些问题:Tail call optimization: enable by default?.

    链接摘录:

    一般来说,尾递归优化包括另一种优化技术——“调用”到“跳转”转换。在我看来, 很难应用这种优化,因为认识到 在 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
    

    【讨论】:

      【解决方案3】:

      它可以有,但不保证:

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

      【讨论】:

      • 链接已失效。
      • @karatedog:谢谢,已更新。虽然说实话,参考可能已经过时了,因为这个错误现在已经有 5 年了,并且从那时起就在同一主题上进行了活动。
      • 是的 :-) 我刚刚阅读了有关该主题的内容,我看到在 Ruby 2.0 中它可以从源代码启用(不再需要修改和重新编译 C 源代码)。
      【解决方案4】:

      TCO 也可以通过在编译前调整 vm_opts.h 中的几个变量来编译: 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
      

      【讨论】:

        【解决方案5】:

        这建立在 Jörg 和 Ernest 的答案之上。 基本上这取决于实现。

        我无法得到 Ernest 对 MRI 工作的回答,但这是可行的。 我发现this example 适用于 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
          p e
        end
        

        【讨论】:

          猜你喜欢
          • 2012-04-18
          • 2011-07-16
          • 2010-11-16
          • 1970-01-01
          • 2020-10-21
          • 1970-01-01
          • 2017-07-23
          • 2018-08-30
          • 2015-07-20
          相关资源
          最近更新 更多