为什么字节码可能比本地代码运行得更快

15

Java速度较慢。

这已经不只是一个“城市传说”,它似乎是一个事实。由于延迟问题,你不会将其用于现场编码,也不会将其用于集群/并行计算。有成千上万的基准测试,尤其是“Java vs C# vs C ++”。

http://benchmarksgame.alioth.debian.org/

根据上述网站,Java的性能不仅几乎与C一样好(比其他语言差得远),而且运行在JVM上的函数式语言Scala和Clojure的性能都比OCaml、Erlang要好。

还有很多“Java比X更快”的例子(例如,在Stack Overflow上的一个问题:Java Runtime Performance Vs Native C / C++ Code?)。

因此,对于某些情况来说,Java似乎很快。有人能解释为什么吗?

为什么在动态代码(如Scala、Clojure)和垃圾回收机制下,字节码有时会比本地代码运行得更快?如果它更快,为什么仍然存在延迟问题?

这似乎是一个矛盾的问题,有人能够解释一下吗?


2
一个合适的垃圾回收机制肯定比任何不平凡的手动内存管理要快得多。 - SK-logic
5
Java虚拟机能够根据应用程序运行期间收集的信息进行优化,这些信息在编译时不可用。 - OscarRyz
创建可管理的对象数量,就不会出现显著的GC开销,如果这是一个真正的担忧,但在90%以上的情况下并非如此。可以构建一个系统,在白天完全不需要进行GC。 - Peter Lawrey
@OscarRyz >>Java虚拟机可以利用应用程序运行期间收集的信息进行优化,而这些信息在编译时不可用<<请注意,GCC和英特尔编译器现在允许一些基于配置文件的优化(PGO)。 - igouy
5个回答

12

在《程序员的智慧》一书中,James Gosling解释道:

James: 没错。现在我们几乎总是能够击败那些非常优秀的C和C++编译器。当你使用动态编译器时,当编译器在最后一刻运行时,你会有两个优势。其中一个是你知道你正在运行哪种芯片组。很多时候,当人们编译一段C代码时,他们必须将其编译成在通用x86架构上运行的代码。几乎没有任何二进制文件特别适合其中任何一种芯片组。你下载Mozilla的最新版本,它几乎可以在任何英特尔架构CPU上运行。而且只有一个Linux二进制文件。它相当通用,并使用GCC编译,这不是一个很好的C编译器。

当HotSpot运行时,它知道您正在运行哪种芯片组。它准确地知道缓存如何工作。它准确地知道内存层次结构如何工作。它完全了解CPU中所有流水线互锁的工作原理。它知道此芯片组具有哪些指令集扩展。它会为您精确地进行优化。然后另一半是它实际上会在应用程序运行时查看应用程序。它能够收集统计信息,知道哪些事情很重要。它能够内联C编译器永远无法完成的事情。在Java世界中被内联的东西相当令人惊讶。然后你将具有现代垃圾收集器的存储管理方式附加到其中。使用现代垃圾收集器,存储分配非常快速。


27
“我们几乎总是能打败那些非常优秀的C和C ++编译器”...“GCC不是一个非常好的C编译器”...“有了现代垃圾回收器,内存分配非常快”...这只是营销和恶意挑衅。 - Arnaud Bouchez
1
正如上面的人所说 - 这只是营销和挑衅。说得好。这不是真的原因。 - Pijusn
1
Java甚至没有提供使用堆栈的方法 - 它如何在性能方面被认真对待呢? - excalibur
即使现在Java的性能大约比C++慢1.5到2倍,比C慢高达3倍。随着编译器/解释器的成熟,它们变得更加高效,因此说字节码解释器比编译器更快是可笑的。"知道运行时信息可以进一步指导优化"这是一个公平的观点,但反过来,将VM分发到数百个平台需要在"通用x86架构"上运行代码。没有人力来为所有这些平台进行微调。 - Aaron3468
1
维基百科也支持我的观点,指出尽管Jake2在某些平台上比C执行速度更快,但它是少数几个可以做到这一点的程序之一,并且基准测试不够透明,无法得出结论。 - Aaron3468
显示剩余5条评论

9
快速的JVM使用Just-In-Time(JIT)编译技术。字节码在运行时动态地被翻译成本地代码。 JIT 提供了许多优化机会。有关 JIT 的更多信息,请参见这篇维基百科文章

很棒的文章,特别是最后的历史部分中的HPA-8000。我之前读过关于它的论文——HPA-8000本质上是在模拟自身,因此速度更快。 - Stephen P

4

JVM有很多种!

最快的一种会根据性能特征实时将字节码编译成本地代码。这需要额外的内存,因此它们以速度为代价换取内存。此外,达到最高速度需要一段时间,所以短暂的程序无法体现其好处。

尽管如此,JamVM解释器(与Oracle JVM相比非常小)仍然可以达到合理分数的编译JVM的最高速度。

关于垃圾回收,速度再次来自于有足够的可用内存。此外,真正的好处在于从代码中删除了跟踪对象何时不再使用的责任。这样可以得到更简单、更少出错的程序。


3
这是一个老话题了,几乎所有流行的编辑器都有,例如Emacs和VI(但肯定不会太老)。我看到很多C++开发人员提供了许多关于为什么大多数性能基准测试(特别是提到Java和C ++速度相同)是有偏差的论点,说实话他们有一定道理。
我没有足够的知识或时间深入研究Java如何可以像C++一样快,但以下是可能的原因...
1- 当您要求两个非常有能力的开发人员为现实世界中的同一个问题编写Java和C++代码时,如果Java比C ++更快,则我会感到惊讶。良好编写的C ++仅使用Java将使用的内存的一小部分。(Java对象略微臃肿)。
2- Java本质上意味着是一种更简单的语言,并且它的编写旨在确保难以编写次优代码。通过抽象内存管理并处理低级优化,使用Java编写良好的代码比使用C ++更容易。(我认为这是最重要的事情....在Java中很难编写糟糕的代码)。另一方面,良好的C ++开发人员可以比Java中的自动GC更好地处理内存管理。(Java将所有内容存储在堆中,因此使用更多内存...)
3- Java编译器已经不断改进,像热点之类的想法已被证明比营销术语更好。(当引入JIT时,它只是一个营销术语..根据我的看法.. :))
4- 根据底层操作系统参数定制设置的人体工程学使Java更好地处理变化。因此,在某些环境中,很容易想象Java的表现与C ++一样好。
5- 为Java开发人员打开高级并发和并行API也是一个原因。 Java并发包可以说是编写能够利用今天的多处理器环境的高性能代码的最简单方法。
6- 随着硬件越来越便宜,开发人员的能力已成为更大的因素,这就是我认为野外的许多C ++代码可能比Java慢的原因。

泛型在字节码级别上不会消除任何类型转换,并且对性能没有影响。 - MeBigFatGuy
是的,你说得对。已经更正了答案。 - uncaught_exceptions

2
Java字节码比大多数本地操作码更容易优化。因为字节码是受限制的,而且不能做一些危险的事情,所以你可以更充分地进行优化。例如指针别名。在C/C++中,你可以这样做:
char[] somebuffer = getABuffer();
char* ptr = &someBuffer[2];
memcpy(ptr, somebuffer, length);

这使得在某些情况下优化变得困难,因为您无法确定正在引用什么。在Java中,这种情况是不允许的。
通常,在更高层次的抽象上可以执行的代码变异比对象代码更强大。

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