JIT为什么不编译大型方法?这是什么原因?

17

我想知道JVM的JIT编译器为什么忽略“巨大的方法”而不被编译。(除非将DontCompileHugeMethods标志设置为false。) 同时,Java JIT编译器的大多数讨论都认为内联是一种超级优化,因为它允许增加可以编译的指令的数量。而这种更大的编译上下文允许更好地优化执行的代码。因此,我认为一个巨大的方法与一个经过高度内联的方法没有太大区别,应该是JIT编译的一个很好的目标。我在这里错过了什么?


1
相关文章:http://blog.leenarts.net/2010/05/26/dontcompilehugemethods/ - user2864740
相关问题:https://dev59.com/zGUo5IYBdhLWcg3wzCBr?rq=1 - Thilo
感谢相关的问答。然而,我主要想知道背后的原理。我知道发生了什么,甚至使用JIT监视器进行了调查。甚至可以将深度调用堆栈“手动内联”到一个方法中,在该方法中,相同的代码序列被JIT编译,但在内联时被忽略。 - Rafael Winterhalter
什么与内联的理念相矛盾,即方法越长,优化效果越好。 - Rafael Winterhalter
@EJB 我听过几次 Oracle JIT 人员的演讲,他们说虚拟调用本身并不昂贵,但是虚拟调用不允许内联,当方法没有变得足够大以应用适当的优化时就会出现这种情况。内联用于使方法“变大”,以便在 JIT 编译过程中对它们进行优化。 - Rafael Winterhalter
显示剩余2条评论
2个回答

10

大致上,编译巨大的方法的回报率很低。

  • 热点代码通常很短。

  • 即使经常执行一个巨大的方法,热点部分也不太可能覆盖整个方法。 例如,考虑一个大的switch语句,只有少数case标签经常被执行。 然而,编译单元是一个方法,因此JITted代码的更大部分将是浪费的。

  • 编译巨大的方法需要花费大量的时间和空间。此外,编译时间并不呈线性增长。 相比之下,编译几个小方法会更加有利。

  • 过长的机器代码污染指令缓存。

  • 某些优化更适用于较小的代码片段,例如寄存器分配或循环展开。

  • 如果单个方法的字节码大于8K,则该方法似乎编写得不好。


1
我怀疑许多方法在编写时不会超过8kb。但是JIT本身是否通过内联方法和展开循环来增加它们的大小呢? - Raedwald
1
@Raedwald JIT 优化是在 IR 级别上执行的,而不是在字节码级别上执行的。尽管 JIT 在决定是否内联方法时会考虑调用者和被调用者的大小。 - apangin

1
Th 8k限制可能只是过时的启发式方法。在[TM]早期,JIT编译有些昂贵(特别是从响应性的角度来看)。事实上,大多数有趣的优化(比如常量折叠、良好的寄存器分配等)都是超线性的。因此,您需要特别小心,不要为了运行一个可能只产生一小部分性能增益的优化任务而停止整个过程半秒钟。

尽管如此,我认为随着经验的积累、更快的处理器和更好的JIT技术,限制可能会有所放宽(甚至可能提高一个数量级),但超线性性能的核心问题仍然存在,因此限制也将存在。


它不应该如此过时;即使有更好的硬件,编写如此庞大的方法也不再明智。总有一种方法可以重构为更易读(和可JIT化)的代码... - Uwe Allner
1
但JIT编译不是一直都是一种投资吗? 它只适用于热方法。 所以即使您花费一秒钟执行JIT,如果经常执行热方法,则可能会得到回报。 另外,JIT在自己的线程上运行,因此延迟应该不是太大的问题。 - Rafael Winterhalter
JIT现在可能会并行运行,但当Hotspot VM被Sun收购时,英特尔开始生产Pentium II。是的,即使JITing需要很长时间,您也可能会获得一些东西。这就是为什么我称其为启发式方法的原因。 - choeger
但是,为什么我要决定简单地命名一个特定的字节数,在那里JIT不再值得呢?这对我来说毫无意义。 - Rafael Winterhalter
因为它是几个优化算法输入大小的度量标准,所以这就是你设置限制的地方。 - choeger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接