Java JIT与AOT性能比较 - foojay


Java代码在运行时被编译Just-In-Time (JIT) 与运行前被编译Ahead-Of-Time (AOT) 区别是什么?
为什么与本机编译的 AOT 应用程序相比,JIT 性能更好?
在这篇文章中,我对这两种策略进行了快速更新,以阐明为什么您会获得不同的性能结果。

什么是Just-In-Time(JIT)?
Java虚拟机执行字节码,它是由你用Java(或其他支持的语言如Kotlin)编写的代码生成的。这种字节码通常被打包成一个JAR文件,运行时可以在任何平台上执行。

在执行过程中,经常使用的方法(热点)被识别并编译成本地代码。这是用两种编译器完成的。C1(快速编译,低优化)和C2(慢速编译,高优化)。这种方法总是为应用程序运行的确切用例和平台编译出性能最好的本地代码。

这种方法的缺点是,在启动时,虚拟机在 "预热时间 "内执行(慢的)字节码,直到它识别出热点并达到完美的本地编译代码。这个过程在每次启动时都会重复。


什么是Ahead-Of-Time(AOT)?
Java代码也可以被编译成本地应用程序,例如使用GraalVM。在这种方法中,你的Java代码被静态编译,而编译器则在特定平台的可执行文件中创建本地代码。

这样做的好处是,在运行时不需要解释字节码,不需要识别热点,也不需要CPU负载进行编译。因此,应用程序将在前面创建的本地编译代码的基础上全速启动。

缺点是,你需要为所有你想运行应用程序的平台进行编译,而且运行时不包含原始代码,不能根据实际使用情况用它来进一步优化应用程序的行为。

不同的方法,不同的性能
如果所有的代码在运行前都被编译了(AOT),那么它的性能怎么会比JIT编译的代码差呢?
这就是JIT方法的真正优势所在!
它可以根据实际需要调整编译后的本地代码,以处理数据或执行其动作。

这些只是所谓的推测性优化的几个例子:

  • 如果你的代码已经被创建为处理几个if或switch案例,但其中有几个选项从未被使用,它们将不会被编译为本地代码,从而使代码更小更快。如果后来发现一些未编译的选项是需要的,编译器可以产生新的本地代码,以最佳方式处理该方法。
  • 在可能的情况下,编译器会用一个公共变量代替私有变量,以减少对getter方法返回属性值的需要。
  • 在Azul,我们已经看到,这种投机性的优化可以带来高达50%的性能提升!

Ahead-Of-Time                  Just-In-Time
- 类加载防止方法内联            + 可以使用积极的方法内联
- 没有运行时字节码生成           + 可以使用运行时字节码生成
- 反射很复杂                  + 反射很(相对)简单
- 不能使用投机性优化             + 可以使用投机性优化
- 总体性能通常会更低             +总体性能通常会更高
+ 一开始就全速运行               - 需要预热时间
+ 在运行时编译代码没有CPU开销       - 在运行时编译代码有CPU开销


总结
AOT和JIT都提供了执行Java代码的好方法。但是,尽管AOT在启动方面提供了一系列的优势,但为了在运行期间获得最佳性能的代码,JIT编译器的影响也不应被低估。