Java、JIT 和垃圾回收器效率

4
我想了解Java的效率以及Java虚拟机和Android的优缺点。效率是指低内存使用,低处理器使用和快速执行。
移动设备比PC简单,因此应用程序需要更高的效率。服务器接收许多连接,它们需要非常高效。许多移动设备使用Android和Java应用程序,许多服务器使用PHP。
Java和解释性语言(例如JavaScript、Python和PHP)是否可以比C和C++更有效?
JIT(即时编译)的优点:
- 它可以更好地进行优化,因为它知道一些变量的值以及它们在哪里使用或更改。 - 它知道处理器并且可以使用处理器特定的指令进行优化。 - 将函数转换为内联函数更容易。 - 它可以删除已知的条件测试并删除不会运行的块。
Java的缺点:
- 当应用程序第一次运行时,由于字节码将被解释并且JIT编译器将进行许多分析以找到良好的优化,因此应用程序将非常慢。应用程序无法使用硬件的最大功率。如果应用程序是游戏或实时应用程序,并且第一次成功运行且没有延迟,但它使用了硬件的最大功率,则下一次运行该应用程序时,由于优化,它将不使用硬件的最大功率。问题在于,应用程序不能被设计为在优化后利用硬件的最大功率,因为它将在第一次运行时太慢,并且不会继续运行。 - Java检查数组索引是否超出范围,并检查指针是否为空。它将向生成的代码添加几个内部“if”。 - 所有对象都使用垃圾收集器,包括非常容易手动删除的对象。 - 所有对象实例都使用动态内存分配,包括可以轻松使用堆栈的对象。如果循环迭代开始创建类的实例并结束删除已创建的对象,则动态内存分配将是低效的。 - 垃圾收集器需要停止应用程序,同时清理内存,这对于游戏、GUI应用程序和实时应用程序非常不希望。引用计数速度较慢,无法处理循环引用。多线程垃圾收集器速度较慢,需要更多地使用CPU。

我不是这方面的专家,所以我会发表评论而不是回复:1)Dalvik并不是真正的JVM,它介于JVM和Google自定义VM之间;2)所有优化都是针对JVM的;Sun/Oracle的HotSpot在空指针检查终止方面非常出色,基本上抵消了你提到的其中一个缺点;3)在你最后一点中,你对Java中所有可用的GC做了很多假设,大多数(如果不是全部)都不适用于所有JVM GC。 - Esko
针对空值检查,可以通过以下单条指令实现:cmp [eax],eax。对于非空对象的情况,在现代处理器中几乎所有情况下,此指令不会导致延迟。如果指针为空,则运行时可以处理段错误以抛出适当的异常。 - Sam Harwell
5个回答

5
Java和解释性语言(如Java Script,Python和PHP)能否比C和C++更高效?
很难比最好的C和C++程序更高效。但有很多C和C++程序远不如这些顶尖程序高效,如果你足够优秀,用现代的Java代码打败它们是相当实际的。 我也听说过目前最好的Javascript引擎表现不错,但我从未详细研究过它们。
对于Python和PHP(以及许多其他语言),情况有所不同。这些语言是用C编写的,因此显然不能比C更高效(按照构造方式)。但在它们中编写高效的代码(即使用实际上是非常精美的C库)比从头开始要容易得多。特别是,它减少了每个程序的缺陷数。这在实践中是一个非常重要的指标;任何人都可以生成快速的代码,如果允许出错的话。
总的来说,我建议不要担心获取最大效率。你会遇到边际收益递减定律。相反,使用合理的整体算法(或者正如我的一个朋友曾经对我说的那样,“关注大O(),让常数因子自己处理”),并关注程序在实践中是否足够好。一旦它达到了,停止瞎折腾,发货吧!

在大多数情况下,程序员的效率(即不浪费时间)比代码效率更重要。对于最后一段文本中的“+1”,请给予翻译。 - Stephen C
1
这些语言是用C编写的。JVM也不是吗?至少其中一部分是用C编写的吧? - ernesto

3
让我们分析一下您所声称的缺点:
当应用程序第一次运行时,应用程序会非常慢,因为字节码将被解释并且JIT编译器将做许多分析以找到最佳优化。应用程序无法使用硬件的最大性能。
JIT编译是一个实现问题,不是所有平台都支持。事实上,可以修改Android平台以进行预编译或缓存JIT生成的本机代码,以在下次运行应用程序时提供更快的启动速度。
有趣的是,各种Java供应商在各个时期尝试了这些策略,然而经验证明,纯JIT是最好的策略。
Java检查数组索引是否超出范围,并检查指针是否为空。它将添加几个内部“if”语句来生成代码。
JIT编译器可以优化掉许多这些测试。对于其余部分,开销往往相对较小,例如差几个百分点...而不是两倍因子。
请注意,检查的替代方案是典型应用程序错误会崩溃Android平台的风险。当然,如果应用程序可以破坏内存,则垃圾收集变得棘手。
所有对象都使用垃圾收集器,包括很容易手动删除的对象。
另一方面,很容易“忘记”删除对象、两次删除对象、在删除后继续使用它们等。这些错误都会导致很难追踪的错误。
所有对象实例都是使用动态内存分配创建的,包括可以轻松使用堆栈的对象。如果循环迭代开始创建类的一个实例并结束删除已创建的对象,则动态内存分配将是低效的。
Java动态内存分配和对象创建是快速的。比如,在C++中更快。
垃圾收集器需要停止应用程序,同时清理内存,这对于游戏、GUI应用程序和实时应用程序非常不期望。
使用并发/低暂停垃圾收集器。另一种方法是实现您的应用程序以不生成大量垃圾,并且很少触发垃圾收集。
参考计数很慢,无法处理循环引用。
没有像样的Java GC使用引用计数。(另一方面,许多C/C++手动内存管理方案会这样做。例如,C++中的所谓智能指针方案。)
您实际上是指并发收集吧。是的,但这是您为互动游戏/实时应用程序要求更高响应性所付出的代价。

2
您所描述的“高效”在我的看法中是“理想”的。一个需要较少内存和CPU时间并且运行速度快的应用程序也就是同时拥有了好、快和便宜三个特点。不用考虑其是否实用或有趣。
只有当所有三个目标都必须达成时,我才会认为唯一合理的比较是那些能够产生共同结果的应用程序。在这种情况下,假设存在一组能力均衡的程序员竞争,任何一个实现都不可能在这三个方面表现得比其他实现更出色。
话虽如此,您的问题却忽略了移动市场的一个关键因素:应用程序开发速度。对于移动应用程序来说,良好的用户体验比后端优化带来的利润更多。如果没有这个限制,您所提出的“高效”的问题在我看来似乎更多地是一个沉重的思考而不是一个实际的问题。
但是,就实际问题而言:像Java这样的语言能否比静态编译到目标机器指令集的语言生成更高效的代码?可能不能。但是,它能否足够高效?绝对可以。如果我们考虑一种执行平台,它具有固定、严格受限的资源,并且不经常更改,那么情况就会不同。

1

无论用什么语言,快速执行任务的方法就是尽量减少执行次数和垃圾回收次数。

这听起来像一个空洞的普遍性观点,但实际上,不管用什么语言,它意味着:

  1. 对于数据结构的设计,要尽可能简单。远离充满花哨的集合类。特别是,要远离使用通知来保持数据一致性的方法。如果您的数据已经规范化,那么它永远不会出现不一致的情况。如果您不能将其规范化,那么容忍暂时的不一致性比试图通过通知来保持数据的紧密性更好。

  2. 即使是最好的代码也会出现性能问题。您应该尽量避免出现这些问题,但是即使避免不了,最重要的是知道如何找到这些问题并解决它们。以下是一份详细的示例。如果在这个过程中发现需要更好的大O算法,那么就去实现它。如果不确定是否需要,就别去实现它,否则会导致程序变慢。

没有一种语言可以从未解决的性能问题中拯救一个程序。语言及其编译器、JITter等就像一匹赛马。想要一匹好马是没问题的,但如果骑手不够苗条,那就是浪费。 你的程序就是骑手,你的工作就是让它参加减肥计划。

1

我将贴出James Gosling在书籍Masterminds of Programming中亲自给出的有趣答案。

据说,在Java世界中,你实际上有两个编译器。一个是编译成Java字节码的编译器,另一个是JIT,它基本上会重新编译所有东西。所有可怕的优化都在JIT里面。
James:完全正确。现在我们几乎总是能打败非常好的C和C++编译器。当你使用动态编译器时,你可以获得两个好处。一是你确切地知道你正在运行的芯片组。很多时候,当人们编译一段C代码时,他们必须将其编译为适用于通用x86体系结构的代码。几乎没有任何二进制文件是特别针对其中任何一个进行了优化的。你下载最新版本的Mozilla,它几乎可以运行在任何英特尔架构CPU上。只有一个Linux二进制文件。它是相当通用的,并且使用的是GCC编译器,这不是一个非常好的C编译器。
当HotSpot运行时,它知道你所运行的芯片组。它精确地知道缓存是如何工作的。它知道内存层次结构是如何工作的。它知道CPU中所有管道互锁的工作方式。它知道这个芯片组有什么指令集扩展。它为你的机器进行精确优化。然后,它实际上看到应用程序正在运行。它能够拥有知道哪些东西是重要的统计数据。它能够内联一些C编译器永远无法完成的事情。在Java世界中内联的东西非常惊人。然后,你加上现代垃圾收集器的存储管理方式。使用现代垃圾收集器,存储分配极其快速。

Masterminds of Programming


“他自己”?这个行业难道不是已经有足够的神坛了吗?无论如何,他提到的所有优点对于代码而言都是很好的,特别是当代码本身是最优的,并且大部分时间都在 CPU 绑定的用户代码中运行,而不是 I/O 或系统调用中。【大多数代码并非最优。】(https://dev59.com/mnNA5IYBdhLWcg3whuV_#927773) - Mike Dunlavey

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