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

时间:2010-09-01 09:07:45

标签: java language-agnostic optimization jvm tail-call-optimization

does-the-jvm-prevent-tail-call-optimizations后两年,似乎prototype implementationMLVM已将该功能列为“proto 80%”一段时间了。

Sun的/ Oracle方面是否没有积极的兴趣来支持尾部调用,或者只是尾部调用“[...] 命中注定要在每个功能优先级列表中排在第二位 [ ......]“正如JVM Language Summit提到的那样?

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

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

5 个答案:

答案 0 :(得分:32)

Diagnosing Java Code: Improving the Performance of Your Java Codealt)解释了为什么JVM不支持尾调用优化。

  

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

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

  

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

然后它给出了一个测试,你可以用来判断你的JIT是否这样做。

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

  

我用几个程序运行了这个程序   Java SDK,结果是   奇怪。在Sun的Hotspot上运行   版本1.3的JVM揭示了这一点   热点不执行   转型。在默认设置下,   堆栈空间耗尽   我的机器上只有一秒钟。在   另一方面,IBM的1.3版JVM   without without without without,,,   表明它确实改变了   以这种方式编码。

答案 1 :(得分: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

答案 2 :(得分: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());
    }
}

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

基本上,这意味着在向后兼容时很难支持这一点。

答案 3 :(得分:12)

Java是你可以想象的功能最少的语言(好吧,好的,perhaps not!)但这对于JVM语言来说是一个很大的优势,比如Scala

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

答案 4 :(得分:0)

这不是 Java 的问题……它是 JVM 的问题之一。 Java 只是 JVM 语言的盛大- ol'-pa。

制作 TCO 是在删除当前堆栈帧的同时跳转到下一个堆栈帧,在正在运行的程序和当前堆栈调用之间的变量应该在其他地方...;)

最好的方法是为其他帧中的跳转调用添加一个新的特殊调用操作码。他们已经为虚拟电话做到了这一点。真的不是解释上的问题,JIT 可能会引发其他问题,JVM 够臃肿了。

在Java或其他语言中,由于没有适当的TCO,另一种方式是蹦床,但它增加了大量代码。或者使用特定的异常,但它很混乱。它在您的代码中,但不在其他人的库中...

啊!如果 Rich Hickey 添加了一个(recur...)东西(它不是一个功能),那是因为缺乏真正的 TCO,他不希望人们认为有一个。他可以很容易地在内部尾呼中自动制定 TCO。它还有助于检测不在尾部位置的坏尾调用。

还有一个(trampoline...)用于外部 TCO 的东西,但它很乱(作为一个蹦床),并且除了在糟糕的堆栈情况外几乎不使用。

但是是的,很多虚拟机都在管理 TCO。我听说 CLR 会。我什至见过管理它的付费 JVM(前段时间,不记得了...)

js 中的蹦床示例:https://codeinjavascript.com/2020/06/13/tail-call-optimization-tco/

关于具有帧覆盖的 HotSpot VM 的 TCO 的旧论文:https://ssw.jku.at/Research/Papers/Schwaighofer09Master/schwaighofer09master.pdf