是什么让最新版本的 JVM 更快?

2022-09-03 04:19:29

我最近看到多个说法,谈论Java(以及基于JVM的语言,如Scala)在性能上如何与C / C++代码相媲美。

例如,从ScalaLab项目的描述中可以看出:

基于Scala的脚本的速度接近本机和优化的Java代码的速度,因此接近甚至优于基于C / C++科学代码!

有人可以给我指出这些JVM优化的摘要吗?是否有任何真正的基准支持这一说法或提供一些现实世界的比较?


答案 1

表演技巧

首先,这取决于你说的是哪个JVM,因为有几个 - 但我假设你的意思是Oracle HotSpot(无论如何,其他顶级JVM将使用类似的技术)。

对于该JVM,HotSpot内部wiki中的这个列表提供了一个很好的开始(子页面详细介绍了一些更有趣的技术)。如果你只是在寻找一个洗衣清单的技巧,维基也有,尽管要理解它们,你可能不得不谷歌个别术语。

并非所有这些都是最近实现的,但一些大的已经实现(范围检查省略,逃逸分析,超级词优化) - 至少对于“最近”的松散定义。

接下来,让我们来看看C/C++与Java的相对性能,以及为什么上述技术有助于缩小差距,或者在某些情况下实际上使Java和内在优势优于本机编译语言。

Java vs C/C++

在较高层次上,优化是您在C和C++等任何像样的本地语言编译器中看到的东西的混合体,以及减少Java / JVM特定功能和安全检查的影响所需的东西,例如:

  • 转义分析,可(在某种程度上)缓解对象的无堆栈分配
  • 内联缓存和类层次结构分析,可缓解“每个函数都是虚拟的”问题
  • 范围检查消除,可减少“每次阵列访问都经过范围检查”的效果

许多这些特定于 JVM 的* 优化仅有助于使 JVM 与本机语言达到奇偶校验,因为它们正在解决本机语言不必处理的障碍。然而,一些优化是静态编译语言无法管理的事情(或者在某些情况下只能通过按配置文件选择来管理,这是罕见的,无论如何都必须是一刀切的):

  • 仅动态内联最热门的代码
  • 基于实际分支/开关频率的代码生成
  • 动态生成 CPU/指令集感知代码(甚至是代码编译后发布的 CPU 功能!1 个
  • 省略从未执行的代码
  • 注入与应用程序代码交错的预取指令
  • 由安全指向支持的整个技术家族

共识似乎是,Java经常在速度上与中等优化级别的良好C++编译器(例如gcc -O2)相似,尽管很大程度上取决于确切的基准测试。像HotSpot这样的现代JVM往往擅长低级数组遍历和数学(只要竞争编译器不矢量化 - 这很难被击败),或者在具有重对象分配的场景中,当竞争代码执行类似数量的分配时(JVM对象分配+ GC通常比malloc快),但是当典型的Java应用程序的内存损失是一个因素时,就会下降, 其中堆栈分配被大量使用,或者矢量化编译器或内部函数将天平向本机代码倾斜。

如果你搜索Java与C的性能,你会发现很多人已经解决了这个问题,具有不同程度的严谨性。这是我偶然发现的第一个,它似乎显示了gcc和HotSpot之间的粗略联系(在这种情况下甚至在-O3)。如果你想看看单个基准测试如何在每种语言中经历多次迭代,相互跳跃,并展示双方优化的一些局限性,那么这篇文章和链接的讨论可能是一个更好的开始。

*好吧,不是真正特定于JVM的 - 大多数也适用于其他安全或托管语言,如CLR


1 随着新指令集(特别是 SIMD 指令,但还有其他指令集)以某种频率发布,这种特定的优化变得越来越重要。自动矢量化可以大大加快一些代码的速度,虽然Java在这里的速度很慢,但它们至少赶上了一点。


答案 2

当然,实际性能取决于基准测试,并且因应用而异。但是,很容易看出JIT VM如何与静态编译代码一样快,至少在理论上是这样。

JIT 代码的主要优势在于它可以根据仅在运行时已知的信息进行优化。在 C 中,当您链接到 DLL 时,每次都必须进行该函数调用。在动态语言中,函数可以内联,即使它是在运行时加载的函数,这要归功于及时编译。

另一个示例是基于运行时值进行优化。在 C/C++,使用预处理器宏禁用断言,如果要更改此选项,则必须重新编译。在 Java 中,通过设置私有布尔字段,然后在代码中放置 if 分支来处理断言。但是,由于 VM 可以根据标志的值编译包含或不包含断言代码的代码版本,因此性能影响很小或没有影响。

另一项主要的 VM 创新是多态内联。Idomatic Java非常注重小型包装器方法,如getters和setters。为了实现良好的性能,显然有必要内联它们。在实际只调用一种类型的常见情况下,VM 内联多态函数不仅可以内联,还可以通过包含具有相应代码的内联缓存来内联调用多个不同类型的代码。如果代码开始在许多不同的类型上运行,VM 可以检测到这一点并回退到较慢的虚拟调度。

静态编译器当然不能做到这一点。强大的静态分析只能让您走得更远。这不仅限于Java,尽管它是最明显的例子。Google的V8 vm for Javascript也非常快。Pypy的目标是为Python和Rubinius为Ruby做同样的事情,但他们并不完全在那里(当你有一个大公司支持你时,它会有所帮助)。


推荐