在JVM中是否有防止尾调用优化两年后,似乎已经有原型 实现,而且MLVM已经将该特性列为“proto 80%”一段时间了。
难道Sun / Oracle方面没有支持尾调用的积极兴趣,还是仅仅因为尾调用被“[...]注定在每个特性优先级列表上都排名第二 [...]”,如JVM Language Summit所提到的那样?
如果有人测试了MLVM版本并能分享一些印象(如果有的话),我会非常感兴趣。
更新:请注意,像Avian这样的某些虚拟机支持适当的尾调用而不会出现任何问题。
在JVM中是否有防止尾调用优化两年后,似乎已经有原型 实现,而且MLVM已经将该特性列为“proto 80%”一段时间了。
难道Sun / Oracle方面没有支持尾调用的积极兴趣,还是仅仅因为尾调用被“[...]注定在每个特性优先级列表上都排名第二 [...]”,如JVM Language Summit所提到的那样?
如果有人测试了MLVM版本并能分享一些印象(如果有的话),我会非常感兴趣。
更新:请注意,像Avian这样的某些虚拟机支持适当的尾调用而不会出现任何问题。
过去在Java中未实现尾递归优化(TCO)的原因之一(并且被视为困难)是JVM中的许可模型是栈敏感的,因此尾调用必须处理安全方面。
我相信Clements和Felleisen [1] [2]已经证明了这不是障碍,而且我很确定问题中提到的MLVM补丁也解决了这个问题。
我意识到这并没有回答你的问题;只是添加了有趣的信息。
诊断Java代码:提高Java代码的性能(备用链接)解释了JVM为什么不支持尾调用优化。
虽然已经很清楚如何将尾递归函数自动转换为简单循环,但Java规范并不要求进行此转换。可能,其原因之一是在面向对象语言中通常无法静态地进行这种转换。相反,必须由JIT编译器动态地将尾递归函数转换为简单循环。
它还举了一个Java代码无法转换的例子。
正如清单3中的示例所示,我们不能指望静态编译器在保留语言语义的同时对Java代码进行尾递归转换。相反,我们必须依赖于JIT的动态编译。取决于JVM,JIT可以或者不可以执行。
然后它给出了一个测试,可以用来确定您的JIT是否支持此功能。
当然,由于这是IBM的论文,它包括一个插头:
我使用了几个Java SDK运行这个程序,结果令人惊讶。在Sun的Hotspot JVM版本1.3上运行,结果显示Hotspot不执行此转换。在默认设置下,堆栈空间在我的机器上不到一秒钟就耗尽了。另一方面,IBM的JVM版本1.3却毫无问题地运行,表明它确实以这种方式转换代码。
也许你已经知道了,但实际上该功能并不像听起来那么简单,因为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());
}
}
尽管这个函数有一个"tail-call"(尾递归),但它可能不会被优化。(即使它被优化,由于程序语义依赖于整个调用堆栈的跟踪,它仍然需要对整个调用堆栈进行簿记。)
基本上,这意味着支持这个功能而仍然保持向后兼容性是很困难的。
g
...考虑到多态性和反射等因素。 - aioobex()
返回的堆栈跟踪(通过getStackTrace()
获得),显示x()
是从y()
调用的?因为如果在此方面存在某些自由,那么就不存在实际问题。 - Raedwald这不是Java的问题,而是JVM的问题。Java只是JVM语言中最古老的。
实现TCO的方法是跳到下一个堆栈帧时删除当前帧,在运行程序和当前调用变量之间,应该有其他地方存储变量... ;)
最好的方法是为在其他帧中进行跳转-调用的新特殊调用操作码,使其完成这些事情。他们已经为虚拟调用做到了这一点。在解释上真的不是个问题,JIT可能会引发其他问题,而JVM已经足够庞大了。
在Java或其他语言中,由于没有适当的TCO,另一种方法是使用trampolining,但它会添加大量代码。或者使用特定的异常,但它会造成很多混乱。而且它存在于你的代码中,但不存在于其他人的库中...
啊!如果Rich Hickey添加了(recur...)东西(它不是一个函数),那是因为缺乏真正的TCO,他不希望人们认为有一个TCO。他可以在内部尾部调用中很容易地实现自动TCO。它还有助于检测不在尾部位置的不良尾部调用。
还有一种外部TCO的(trampoline...)东西,但它很混乱(就像蹦床),除了在糟糕的堆栈情况下几乎没有使用。
没错,很多虚拟机都管理TCO。我听说CLR会这样做。我甚至看到过一个付费的JVM也可以管理它(以前的事情,记不清了...)
JavaScript中的跳板示例:https://codeinjavascript.com/2020/06/13/tail-call-optimization-tco/
关于HotSpot VM上帧覆盖的TCO的旧论文:https://ssw.jku.at/Research/Papers/Schwaighofer09Master/schwaighofer09master.pdf