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

时间:2013-04-22 03:17:18

标签: java performance scala jvm native-code

我最近看到多个声明,谈论Java(以及基于JVM的语言,如Scala)在性能上与C / C ++代码的对比。

例如,从ScalaLab project

的描述中
  

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

有人能指出我对这些JVM优化的概述吗?是否有任何真正的基准支持这一主张或提供一些真实的比较?

3 个答案:

答案 0 :(得分:22)

性能技术

首先,它取决于你正在谈论的哪个 JVM,因为有几个 - 但我会假设你的意思是Oracle HotSpot(在任何情况下,其他顶级JVM)将使用类似的技术)。

对于那个JVM,来自HotSpot内部wiki的this list提供了一个很好的开始(并且子页面详细介绍了一些更有趣的技术)。如果你只是在寻找一个技巧的清单,wiki has that too,虽然要了解它们,你可能不得不谷歌个别条款。

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

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

Java vs C / C ++

从较高的层面来看,优化是你在任何体面的编译器中看到的混合物,如C和C ++等本机语言,以及减少Java / JVM特定功能和安全性影响所需的东西检查,例如:

  • 逃避分析,减轻(某种程度上)对象的无堆栈分配
  • 内联缓存和类层次结构分析,它可以缓解“每个功能都是虚拟的”
  • 范围检查消除,减轻“每个阵列访问范围已检查”

这些特定于JVM的*优化中的许多只能帮助使JVM与本地语言保持一致,因为它们正在解决本地语言无需处理的障碍。然而,一些优化是静态编译的语言无法管理的事情(或者在某些情况下只能通过配置文件引导的optmization进行管理,这种情况很少见且无论如何都必须适合所有人):

  • 仅动态内联最热门的代码
  • 基于实际分支/切换频率的代码生成
  • 动态生成CPU /指令集识别代码(甚至编译代码后发布的CPU功能!) 1
  • 从未执行过的代码的删除
  • 注入与应用程序代码交错的预取指令
  • 安全点支持的全系列技术

一致认为,Java通常会产生与中等优化级别的优秀C ++编译器类似的代码,例如gcc -O2,尽管很大程度上取决于确切的基准。像HotSpot这样的现代JVM倾向于在低级别数组遍历和数学方面表现优异(只要竞争编译器没有向量化 - 这很难被击败),或者在竞争代码执行相似数量的分配时具有大量对象分配的情况下(JVM对象分配+ GC通常比malloc更快),但是当典型Java应用程序的内存损失是一个因素,其中堆栈分配被大量使用,或者向量化编译器或内在函数将缩放转向本机代码时,它就会崩溃。 / p>

如果您搜索Java vs C性能,您会发现很多人已经解决了这个问题,并且具有不同程度的严谨性。这是first one I stumbled across,它似乎显示了gcc和HotSpot之间的粗略联系(在这种情况下甚至在-O3)。 This post如果您想了解单个基准测试如何通过每种语言进行多次迭代,相互跳跃,并且显示双方优化的一些限制,那么链接的讨论可能是一个更好的开始。 p>

*确实不是特定于JVM的 - 大多数也适用于其他安全或CLR等托管语言


1 随着新指令集(特别是SIMD指令,但有others)以某种频率发布,这种特殊优化变得越来越重要。自动矢量化可以加速某些代码大量,虽然Java在这里已经慢了标记,但它们至少有catching up一点。

答案 1 :(得分:13)

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

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

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

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

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

答案 2 :(得分:1)

我想补充一点,hotspot,jrockit和IBM的JVM都在GC中执行堆压缩。由于这个原因,我最近将一些重型数学代码移植到Scala。如果您打算运行任何大型应用程序,我会强烈推荐Java。在部署到服务器或扩展时,您可能会后悔使用CLR,尤其是在内存密集的情况下。

此外,对于本机代码,JVM配置选项非常出色。

相关问题