JVM 会阻止尾调用优化吗?

Posted

技术标签:

【中文标题】JVM 会阻止尾调用优化吗?【英文标题】:Does the JVM prevent tail call optimizations? 【发布时间】:2010-09-11 11:22:41 【问题描述】:

我在这个问题上看到了这句话:What is a good functional language on which to build a web service?

特别是 Scala 不支持尾调用消除,除非在自递归函数中,这限制了您可以执行的组合类型(这是 JVM 的基本限制)。

这是真的吗?如果是这样,那么造成这种基本限制的 JVM 是什么?

【问题讨论】:

【参考方案1】:

这篇文章:Recursion or Iteration? 可能会有所帮助。

简而言之,由于安全模型和需要始终提供可用的堆栈跟踪,尾调用优化很难在 JVM 中进行。这些要求理论上可以得到支持,但可能需要一个新的字节码(参见John Rose's informal proposal)。

Sun bug #4726340 也有更多讨论,评估(从 2002 年开始)到此结束:

我相信这仍然可以完成,但这不是一项小任务。

目前,Da Vinci Machine 项目正在进行一些工作。尾调用子项目的状态列为“proto 80%”;它不太可能进入 Java 7,但我认为它很有可能进入 Java 8。

【讨论】:

我没有完全按照解释。我认为尾调用优化是由编译器实现的。假设您有一个可以由编译器进行尾调用优化的函数,那么您也可以有一个等效的非递归函数,该函数使用循环实现相同的功能,对吗?如果是这样,这不能由编译器完成。我无法遵循对 JVM 的依赖。这与生成原生 i386 代码的 Scheme 编译器相比如何? @Gautham:我关于调试的声明是指使用蹦床作为 JVM 上缺少尾调用消除的解决方法。尾调用消除可以并且已经在 J​​VM 上实现(Arnold Schaighofer 在 OpenJDK 和 LLVM 中实现了),所以它是否可以实现是毫无疑问的。当然,微软的 CLR 已经支持消除尾调用 10 年,而 F# 的发布证明它是一个游戏规则的改变者。我想答案是JVM早就停滞不前了。 这是一个常见的误解和经常重复的借口,但不正确。多年来,通过堆栈检查(以及提供有用的堆栈跟踪)实现的安全性与正确的尾调用并非不兼容,这一点已得到公认。例如,请参阅 2004 年的这篇论文。citeseerx.ist.psu.edu/viewdoc/… 否决,因为答案不正确。 @JustinSheehy:什么不正确?问题是,“JVM 会阻止尾调用优化吗?”答案是,“不,但这很难。” 你知道java8中是否包含这个吗?【参考方案2】:

基本的限制只是 JVM 在其字节码中不提供尾调用,因此,构建在 JVM 上的语言没有直接的方法来提供尾调用本身。有一些变通方法可以达到类似的效果(例如蹦床),但它们的代价是糟糕的性能和混淆生成的中间代码,从而使调试器无用。

因此,在 Sun 在 JVM 本身中实现尾调用之前,JVM 无法支持任何生产质量的函数式编程语言。他们已经讨论了多年,但我怀疑他们是否会实现尾调用:这将非常困难,因为他们在实现此类基本功能之前过早地优化了他们的虚拟机,而且 Sun 的工作重点是动态语言而不是函数式语言。

因此,有一个非常强烈的论点,即 Scala 不是一种真正的函数式编程语言:自 30 多年前首次引入 Scheme 以来,这些语言将尾调用视为一项基本功能。

【讨论】:

Hence there is a very strong argument that Scala is not a real functional programming language - 这个论点实际上很弱。当然是tail calls [as] an essential feature,如果底层硬件(或虚拟机)直接支持它,那就太好了。但这是实现细节。 @Ingo:仅当您不认为用户在运行时程序中的堆栈溢出是一个重大问题时。根据它的错误跟踪器,甚至 Scala 编译器本身也受到堆栈溢出的困扰。因此,即使是经验最丰富的 Scala 开发人员仍然会犯错…… 可以成为 F# 的倡导者。但是我已经注意到你很长一段时间(甚至早在 usenet 中)对所有非 F# 的东西都怀有敌意,但你的阐述表明你不知道你在说什么。就像这里:你的论点似乎是我可以编写一个因堆栈溢出而中止的程序的语言不是一种功能性的语言?但是对于我可以引发堆溢出的语言,不能提出相同的论点吗?因此,神圣的 F# 本身不算是功能性的。 @Ingo:函数式编程中的几个习语,如相互递归和连续传递风格,可能需要消除尾调用才能工作。没有它,您的程序将堆栈溢出。如果一种语言不能可靠地运行惯用的函数式代码,它是函数式的吗?正如你所说,答案是一个判断电话,但在实践中是一个重要的区别。 Martin Trojer 刚刚发表了一篇有趣的博文:martinsprogrammingblog.blogspot.com/2011/11/… 仍然,仅仅因为 JVM(很遗憾,毫无疑问)不能进行尾调用,这并不意味着不可能消除尾调用。这就好像有人说浮点计算只能在具有 FPU 的计算机上进行。【参考方案3】:

Scala 2.7.x 支持对 final 方法和局部函数的自递归(一个函数调用自身)进行尾调用优化。

Scala 2.8 也可能带有对 trampoline 的库支持,这是一种优化相互递归函数的技术。

关于 Scala 递归状态的大量信息可以在 Rich Dougherty's blog 中找到。

【讨论】:

能否请您更新有关当前 scala 状态的问题? @om-nom-nom AFAIK,没有任何改变,无论是在 Scala 端,还是在 JVM 端。【参考方案4】:

除了 Lambda The Ultimate 中链接的论文(来自上面发布的 mmyers 链接),来自 Sun 的 John Rose 对尾调用优化还有更多要说的。

http://blogs.oracle.com/jrose/entry/tail_calls_in_the_vm

我听说有一天它可能会在 JVM 上实现。在达芬奇机器上正在研究尾呼叫支持等。

http://openjdk.java.net/projects/mlvm/

【讨论】:

【参考方案5】:

所有消息来源都指向 JVM 在尾递归的情况下无法优化,但在阅读 Java performance tuning (2003, O'reilly) 后,我发现作者声称他可以通过实现尾递归来实现更高的递归性能。

您可以在第 212 页找到他的声明(搜索“尾递归”应该是第二个结果)。什么给了?

【讨论】:

IBM 在其 JVM 实现中支持某种形式的 TCO(作为一种优化,因此无法保证)。也许 Java 性能调优的作者认为这个特性最终会被所有 JVM 实现。 ibm.com/developerworks/java/library/j-diag8.html

以上是关于JVM 会阻止尾调用优化吗?的主要内容,如果未能解决你的问题,请参考以下文章

有啥能阻止优化尾递归?

如何解决栈溢出

对话框会阻止其余代码执行吗?

在 JVM 级别阻止类的实例?

WKCompositingView 会阻止触摸吗?

尝试/最终阻止与调用处置?