为什么JVM仍然不支持尾调用优化?

soc*_*soc 95 java language-agnostic optimization jvm tail-call-optimization

jvm-prevent-tail-call-optimization之后两年,似乎有一个原型 实现,MLVM已经将该功能列为"proto 80%"一段时间了.

Sun的/ Oracle方面是否没有积极的兴趣支持尾调用,或者只是尾部调用" 在每个功能优先级列表中排在第二位 [...]"如JVM所述语言峰会

如果有人测试了MLVM构建并且可以分享它的工作原理(如果有的话),我会非常感兴趣.

更新: 请注意,像Avian这样的某些虚拟机支持正确的尾部调用,没有任何问题.

emo*_*ory 32

诊断Java代码:提高Java代码的性能(alt)解释了JVM不支持尾调用优化的原因.

但是,尽管众所周知如何将尾递归函数自动转换为简单循环,但Java规范并不要求进行此转换.据推测,不是要求的一个原因是,通常,转换不能在面向对象的语言中静态地进行.相反,从尾递归函数到简单循环的转换必须由JIT编译器动态完成.

然后给出了一个不会转换的Java代码示例.

因此,正如清单3中的示例所示,我们不能指望静态编译器在保留语言语义的同时对Java代码执行尾递归转换.相反,我们必须依靠JIT的动态编译.根据JVM,JIT可能会也可能不会这样做.

然后它给出了一个测试,您可以用它来确定您的JIT是否这样做.

当然,由于这是一篇IBM论文,它包含一个插件:

我用几个Java SDK运行了这个程序,结果令人惊讶.在Sun的Hotspot JVM上运行版本1.3显示Hotspot不执行转换.在默认设置下,堆栈空间在我的机器上耗尽不到一秒钟.另一方面,IBM的版本1.3的JVM没有问题,表明它确实以这种方式转换代码.

  • FWIW,尾调用不仅仅是他所暗示的自递归函数.尾调用是出现在尾部位置的任何函数调用.它们不必是对自己的调用,也不必调用静态已知的位置(例如,它们可以是虚方法调用).如果尾调用优化在一般情况下正确完成,他描述的问题是非问题,因此,他的示例在支持尾调用的面向对象语言(例如OCaml和F#)中完美地工作. (62认同)
  • "一般来说,转型不能用面向对象的语言进行静态转换." 这当然是引用,但每当我看到这样的借口时,我想询问数字 - 因为如果在大多数情况下实际上可以在编译时建立它,我不会感到惊讶. (11认同)
  • 引用文章的链接现在已被破坏,尽管Google确实已将其缓存.更重要的是,作者的推理是错误的.如果只有编译器插入`instanceof`检查以查看`this`是否是`Example`对象(而不是子类),那么给出的示例可以*尾部调用优化,使用static和*not*just动态编译. `Example`). (5认同)
  • 只需链接到webarchive http://web.archive.org/web/20120506085636/http://www.ibm.com/developerworks/java/library/j-diag8/index.html (4认同)
  • "必须由JIT编译器动态完成",这意味着它必须由JVM本身而不是Java编译器完成.但OP正在询问JVM. (3认同)
  • 作为参考,本文的测试类是`public class TailRecursionTest {private static int loop(int i){return loop(i); public static void main(String [] args){loop(0); } (2认同)

Ale*_*ler 30

我之前看到的在Java中未实现TCO(并且被视为困难)的一个原因是JVM中的权限模型是堆栈敏感的,因此尾调用必须处理安全方面.

我相信这显示不是Clements和Felleisen [1] [2]的障碍,我很确定问题中提到的MLVM补丁也会处理它.

我意识到这不能回答你的问题; 只是添加有趣的信息.

  1. http://www.ccs.neu.edu/scheme/pubs/esop2003-cf.pdf
  2. http://www.ccs.neu.edu/scheme/pubs/cf-toplas04.pdf

  • +1。收听 Brian Goetz 演讲结束时的问题/回答 https://www.youtube.com/watch?v=2y5Pv4yN0b0&t=3739 (2认同)

aio*_*obe 14

也许您已经知道这一点,但是这个功能并不像听起来那么简单,因为Java语言实际上将堆栈跟踪暴露给程序员.

考虑以下程序:

public class Test {

    public static String f() {
        String s = Math.random() > .5 ? f() : g();
        return s;
    }

    public static String g() {
        if (Math.random() > .9) {
            StackTraceElement[] ste = new Throwable().getStackTrace();
            return ste[ste.length / 2].getMethodName();
        }
        return f();
    }

    public static void main(String[] args) {
        System.out.println(f());
    }
}
Run Code Online (Sandbox Code Playgroud)

即使这有"尾调",也可能无法优化.(如果它优化,它仍然需要整个调用堆栈的簿记,因为程序的语义依赖于它.)

基本上,这意味着在向后兼容的同时很难支持它.

  • 在你的想法中发现了错误:"需要对整个调用堆栈进行簿记,因为程序的语义依赖于它".:-)这就像新的"抑制异常".依赖这些事情的计划必然会破裂.在我看来,程序的行为是绝对正确的:丢弃堆栈帧是尾部调用的全部内容. (17认同)
  • 这只是一个单一方法规范的措辞问题,从"给你所有堆栈帧"到"给你所有活动的堆栈帧,省去尾部调用废弃的那些".此外,无论尾部调用是否受到尊重,都可以使其成为命令行开关或系统属性. (8认同)
  • "语言暴露调用堆栈这一事实使得实现它变得很困难":语言是否要求`getStackTrace()`从源代码显示的方法`x()`返回的堆栈跟踪被调用从方法`y()`也表明从`y()`调用`x()`?如果有一些自由就没有真正的问题. (6认同)
  • @Marco,但几乎任何方法都可能抛出一个异常,整个调用堆栈必然是可用的,对吧?此外,你不能事先决定在这种情况下哪些方法会间接调用`g` ...例如考虑多态和反射. (4认同)
  • 这是由Java 7中添加ARM引起的副作用.例如,您不能依赖上面显示的内容. (2认同)
  • @soc,只是因为*某些*新功能打破了一些向后兼容性,并不意味着如果其他功能也会破坏向后兼容性也没有任何区别......不,我的回答没有错误.堆栈跟踪在API中暴露的事实给(软)尾调用递归带来了问题,并且是一个(很多)原因[为什么这样的功能很难添加](http://blogs.sun.com/jrose /项/ tail_calls_in_the_vm). (2认同)

oxb*_*kes 12

Java是你可能想象的功能最少的语言(好吧,好吧,也许不是!)但这对于像Scala这样的JVM语言来说是一个很大的优势.

我的观察是,使JVM成为其他语言的平台似乎从未成为Sun优先级列表的首选,我想,现在对于Oracle来说.

  • @Thorbjørn - 我编写了一个程序来预测任何给定的程序是否会在有限的时间内停止.我花了*年龄*! (16认同)
  • 但你结束了吗? (4认同)
  • 我使用的第一个BASIC没有功能,而是GOSUB和RETURN.我认为LOLCODE也不是很实用(你可以用两种方式来理解). (3认同)
  • "使JVM成为其他语言的平台似乎永远不会成为Sun优先级列表的首选".与使用函数式语言相比,他们将JVM作为动态语言的平台付出了相当大的努力. (3认同)
  • @ThorbjørnRavnAndersen:不,但这是一个先决条件,你不是吗? (2认同)