【问题标题】:Java, JIT and Garbage Collector efficiencyJava、JIT 和垃圾收集器效率
【发布时间】:2011-04-09 18:37:53
【问题描述】:

我想了解Java的效率以及Java虚拟机和Android的优缺点。 效率就是内存占用少,处理器占用少,执行速度快。

移动设备比 PC 更简单,因此应用程序需要更高效。服务器接收许多连接,它们需要非常高效。许多移动设备使用 Android 和 Java 应用程序,许多服务器使用 PHP。

Java 和解释型语言(如 Java Script、Python 和 PHP)能否比 C 和 C++ 更高效?

JIT(即时)优势:

  • 它可以更好地优化,因为它知道某些变量的值以及在哪里使用或更改。
  • 它了解处理器并可以使用处理器特定指令进行优化。
  • 将函数转换为内联函数更容易。
  • 它可以删除已知的条件测试并删除不会运行的块。

Java 的缺点:

  • 当应用程序第一次运行时,应用程序会很慢,因为字节码会被解释,JIT 编译器会做很多分析以找到好的优化。应用程序无法使用最大的硬件功率。如果一个应用程序是游戏或实时应用程序,如果它第一次运行成功并且没有延迟,但它使用了最大的硬件功率,那么下次运行该应用程序时,它不会使用由于优化,最大的硬件功率。问题是优化后的应用程序无法设计为使用最大的硬件功率,因为​​它在第一次运行时会太慢,并且不会继续运行。
  • Java 检查数组索引是否超出范围,并检查指针是否不为空。它会在生成的代码中添加几个内部“if”。
  • 所有对象都使用垃圾回收器,包括非常容易手动删除的对象。
  • 所有对象实例都是通过动态内存分配创建的,包括可以轻松使用堆栈的对象。如果循环迭代开始创建类的实例并结束删除创建的对象,动态内存分配将效率低下。
  • 垃圾收集器需要在清理内存时停止应用程序,这对于游戏、GUI 应用程序和实时应用程序来说是非常不受欢迎的。引用计数很慢,它不能处理循环引用。多线程垃圾收集器速度较慢,需要更多的 CPU 使用。

【问题讨论】:

  • 我不是这方面的专家,所以我只是发表评论而不是回复,但 1) Dalvik 并不是真正的 JVM,它介于 JVM 和 Google 自定义 VM 之间 2) 所有优化是特定于 JVM 的; Sun/Oracle 的 HotSpot 非常擅长空值检查终止,它基本上可以消除(hah!)你提到的那些缺点之一 3)你对所有可用于 Java 的 GC:s 做了很多假设点,它们中的大多数(如果不是全部)并不适用于所有 JVM GC:s。
  • 跟进空检查,它们可以通过以下单个指令实现:cmp [eax], eax。对于非空对象的情况,该指令在现代处理器上几乎所有情况下都不会导致延迟。如果指针为空,则运行时可以处理 seg 错误以引发适当的异常。

标签: java garbage-collection performance jit


【解决方案1】:

Java 和解释型语言(如 Java Script、Python 和 PHP)能否比 C 和 C++ 更高效?

很难比最好的 C 和 C++ 程序更高效。有很多 C 和 C++ 程序远没有那么高效,如果你做得好的话,用(现代)Java 代码击败它们是非常实用的。 我也听说过有关当前最佳 Javascript 引擎的好消息,但我从未详细研究过它们。

使用 Python 和 PHP(以及除此之外的许多其他语言)有点不同。这些语言是用 C 编写的,所以很明显它们不能比 C 更有效(其次是构造)。然而,在其中编写高效的代码(即使用实际上编写得非常好的 C 库)比从头开始要容易得多。特别是,它减少了每个程序的缺陷数量。这在实践中是一个非常重要的指标。如果允许出错,任何人都可以快速编写代码。

一般来说,我建议不要担心获得最大效率。你违反了收益递减规律。相反,使用合理的整体算法(或者,正如我的一个朋友曾经对我说的,“照顾大 O(),让常数因素照顾自己”)并专注于程序是否足够好的问题在实践中。一旦它是,停止摆弄并运送它!

【讨论】:

  • +1 表示最后一段。在大多数情况下,程序员的效率(即不浪费你的时间)比代码效率更重要。
  • “那些语言是用C写的”……JVM不也是用C写的吗?至少,一部分?
【解决方案2】:

让我们来分析一下你声称的缺点:

  • 当应用程序第一次运行时,应用程序会很慢,因为字节码会被解释,JIT 编译器会做很多分析以找到好的优化。应用程序无法使用最大的硬件功率。

JIT 编译是一个实现问题。并非所有平台都这样做。实际上,Android 平台可以修改为 1) 提前编译,或 2) 缓存 JIT 生成的本机代码,以便下次运行应用程序时更快启动。

有趣的是,不同的 Java 供应商在不同时期都尝试过这些策略,但经验证据表明,简单的 JIT 是最好的策略。

  • Java 检查数组索引是否超出范围,并检查指针是否不为空。它会在生成的代码中添加几个内部“if”。

JIT 编译器可以优化掉其中的许多测试。其余的,间接费用往往相对较小;例如几个百分点的差异......不是 2 倍。

请注意,检查的替代方法是典型应用程序错误会导致 android 平台崩溃的风险。当然,如果应用程序可以回收内存,垃圾回收就会出现问题。

  • 所有对象都使用垃圾收集器,包括非常容易手动删除的对象。

另一方面是很容易忘记删除对象,删除对象两次,删除后使用它们等等。这些错误都会导致难以追踪的错误。

  • 所有对象实例都是通过动态内存分配创建的,包括可以轻松使用堆栈的对象。如果循环迭代开始创建类的实例并结束删除创建的对象,动态内存分配将效率低下。

Java 动态内存分配和对象创建速度很快。例如,比 C++ 更快。

  • 垃圾收集器需要在清理内存时停止应用程序,这对于游戏、GUI 应用程序和实时应用程序来说是非常不受欢迎的。

然后使用并发/低暂停垃圾收集器。另一种方法是让您的应用程序不会产生大量垃圾......并且很少触发垃圾收集。

  • 引用计数很慢,无法处理循环引用。

没有像样的 Java GC 使用引用计数。 (另一方面,很多 C/C++ 手动内存管理方案都是这样做的。例如,C++ 中所谓的智能指针方案。)

  • 多线程垃圾收集器速度较慢,需要更多地使用 CPU。

我认为您实际上是指并发收集。是的,但这是您为互动游戏/实时应用所需的额外响应而付出的代价。

【讨论】:

    【解决方案3】:

    您所说的“高效”我将其描述为“理想”。换句话说,一个需要很少内存、很少 CPU 时间并且运行速度很快的应用程序是一个同时又好、快、便宜的应用程序。没关系它是否有任何有用或有趣的事情。

    如果需要所有三个目标,我认为唯一合理的比较是在产生共同结果的应用程序之间进行比较。在这种情况下,鉴于一群能力相同的程序员相互竞争,任何一种实现都不太可能在所有三个方面都优于其他实现。

    也就是说,您的问题忽略了移动市场的一个关键标准:应用程序开发速度。与后端优化相比,移动应用程序从积极的用户体验中获益更多。如果没有这种限制,你所说的效率问题在我看来更像是一个沉重的考虑,而不是一个实际的考虑。

    但是对于实际的问题:像 Java 这样的语言能否生成比静态编译到目标机器指令集的更高效的代码?可能不是。它可以同样高效,还是足够高效?绝对地。如果我们考虑一个执行平台具有固定的、严重受限的资源且不经常更改,那将是另一回事。

    【讨论】:

      【解决方案4】:

      在任何语言中,获得快速执行的方法是尽可能少地执行任务,并尽可能少地收集垃圾。

      这听起来像是一个空洞的概括,但它在实践中意味着什么,无论语言如何,

      1. 对于数据结构设计,尽量保持简单。远离那些花里胡哨的花哨收藏课。特别是远离通知作为保持数据一致的一种方式。如果你的数据是标准化的,它就永远不会不一致。如果你不能正常化它,最好容忍暂时的不一致,而不是试图通过通知保持紧密。

      2. 性能问题会蔓延,甚至是最好的代码。你应该尽量不要制造它们,但你仍然会制造它们。最重要的是知道如何找到它们,一旦制作并删除它们。 Here's a blow-by-blow example. 如果在执行此操作时,您发现需要更好的 big-O 算法,然后将其放入。在不确定是否需要的情况下放入其中会导致速度变慢。

      没有一种语言可以将程序从未消除的性能问题中解救出来。该语言及其编译器、JITter 等就像一匹赛马。想要一匹好马很好,但如果骑师没有尽可能苗条,那就太浪费了。 你的计划是骑师,你的工作就是把它带入减肥计划。

      【讨论】:

        【解决方案5】:

        我将詹姆斯·高斯林本人给出的有趣答案粘贴到书中Masterminds of Programming

        嗯,我听说过 实际上你有两个编译器 Java 世界。你有编译器 到 Java 字节码,然后你有 你的 JIT,它基本上会重新编译 一切都特别清楚。所有的 你可怕的优化在 JIT.

        詹姆斯:没错。这些天我们 击败真正优秀的 C 和 C++ 编译器几乎总是。当你 去动态编译器,你得到 编译器的两个优点 在最后一刻运行。一 你知道到底是什么芯片组吗 你正在运行。很多时候当 人们正在编译一段 C 代码,他们必须编译它才能运行 在通用 x86 上 建筑学。几乎没有 你得到的二进制文件特别好 为其中任何一个进行了调整。你下载 Mozilla 的最新版本,它会 几乎可以在任何英特尔上运行 架构 CPU。有很多 一个 Linux 二进制文件。很一般, 它是用 GCC 编译的,即 不是一个很好的 C 编译器。

        当 HotSpot 运行时,它确切地知道 你正在运行什么芯片组。它 确切地知道缓存是如何工作的。它 确切地知道内存层次结构 作品。它确切地知道所有的 流水线互锁在 CPU 中工作。 它知道什么指令集 该芯片具有的扩展。它 精确优化什么机器 轮到你了。然后是另一半 是它实际上看到了 应用程序在运行时。它能够 有统计数据知道哪些 事情很重要。它能够 C 编译器可以内联的东西 永远不会做。得到的东西 Java 世界中的内联很漂亮 惊人的。然后你把它附加到 存储管理的工作方式 现代垃圾收集器。带一个 现代垃圾收集器,存储 分配速度极快。

        【讨论】:

        • “他自己”?我们在这个行业没有足够的基座吗?无论如何,他提到的所有这些美妙之处都非常适合那些在其他方面是最优的并且恰好将大部分时间花在受 CPU 限制的用户代码上的代码,而不是 I/O 或系统调用。 Most code is not otherwise optimal.
        猜你喜欢
        • 1970-01-01
        • 2011-01-06
        • 2011-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-13
        • 1970-01-01
        • 2010-10-04
        相关资源
        最近更新 更多