【问题标题】:Performance in Java through code? [closed]通过代码在 Java 中表现? [关闭]
【发布时间】:2020-07-23 23:02:44
【问题描述】:

首先我要提一下,我知道性能优化可以是非常特定于项目的。我现在大多没有面临这些特殊问题。我面临着 JVM 本身的一系列性能问题。

我现在想知道:

  • 哪些代码优化有意义 从编译器的角度来看:对于 支持垃圾的例子 收集器我将变量声明为 最终 - 非常遵循 PMD 的 来自 Eclipse 的建议。
  • 有哪些最佳实践:vmargs, 堆和其他东西传递给 用于初始化的 JVM。如何得到 这里的正确价值观?有没有 公式还是尝试和错误?

Java 自动化了很多,在字节码级别和其他东西上做了很多优化。但是我认为其中大部分必须由开发人员计划才能工作。

那么如何加快 Java 程序的速度呢? :)

【问题讨论】:

  • 将变量声明为 final 对编译器的帮助不如对不熟悉的维护者的帮助。您作为程序员郑重宣誓,您知道(维护者也应该知道)以下代码中不会更改此值。
  • 嗯,好的。我认为这可能会对 GC 有所帮助。
  • 什么是“PMD”?解释/链接您的缩写。
  • 这个问题对于 Stack Exchange 来说似乎过于宽泛和开放。投票结束。

标签: java performance optimization


【解决方案1】:

从编译器的角度来看,哪种代码优化是有意义的:例如,为了支持垃圾收集器,我将变量声明为 final - 非常遵循 PMD 在 Eclipse 中的建议。

假设您正在谈论可以对代码进行的潜在微优化,答案几乎是没有。提高应用程序性能的最佳方法是运行分析器以找出性能瓶颈所在,然后确定是否可以采取任何措施来加速它们。

在大多数情况下,所有经典技巧,例如声明类、变量和方法 final、重新组织循环、更改原始类型等都是浪费精力。 JIT 编译器通常可以比您做得更好。例如,最近的 JIT 编译器将分析所有已加载的类,以确定哪些方法调用不会受到重载,而无需将类或方法声明为 final。然后它将使用更快的调用序列,甚至内联方法体。

确实,Sun 专家表示,一些程序员尝试优化失败是因为它们实际上使 JIT 编译器更难应用它所知道的优化。

另一方面,更高级别的算法优化绝对是值得的……前提是您的分析器告诉您您的应用程序在该代码区域花费了大量时间。

在不寻常的情况下,使用数组而不是集合可能是值得优化的,在极少数情况下使用对象池也可能如此。但是这些优化 1) 会使您的代码更加复杂和容易出错,并且 2) 如果使用不当,可能会减慢您的应用程序的速度。这些类型的优化只能作为最后的手段来尝试。例如,如果您的分析表明某某 HashMap<Integer,Integer> 是 CPU 瓶颈或内存占用,那么最好寻找一个现有的专用 MapMap 类库类而不是尝试并使用数组自己实现地图。换句话说,在高层进行优化。

如果您花费的时间足够长或您的应用程序足够小,那么仔细的微优化可能会给您提供比仅依赖 JIT 编译器更快的应用程序(在给定的 JVM 版本/硬件平台上)。如果您正在实现一个小型应用程序以在 Java 中进行大规模数字运算,那么微优化的回报可能是相当可观的。但这显然不是典型案例!对于典型的 Java 应用程序,工作量足够大,性能差异也足够小,以至于微优化是不值得的。

(顺便说一句,我不明白声明一个变量会对 GC 性能产生什么影响。GC 每次遇到变量时都必须跟踪它,无论它是否是最终的。此外,这是一个公开的秘密最终变量实际上可以在某些情况下发生变化,因此 GC 假设它们没有变化是不安全的。不安全就像“创建一个导致 JVM 崩溃的悬空指针”。)

【讨论】:

  • 根据 Joshua Bloch (Effective Java) 和 Brian Goetz (Java Concurrency In Practice) 将成员变量声明为 final 几乎总是一个好主意(尽管主要是为了保留对象的部分状态不变)。虽然这通常可能不是一个好主意,但直接使用数组可以产生巨大的影响。 P.S.:我赞成你的帖子,因为你的大部分观点都是有效的。
  • 是的 - 将变量声明为 final 一个好主意。但出于与性能无关的原因。如果javac 可以确定将someVar 声明为final 是合法的,那么JIT 编译器也可以确定someVar 可以被声明为 final ...并优化相应地。
  • 使用相同的标识符几乎可以完全透明地对数组引用进行遮蔽:final [TYPE] myArrayRef = this.myArrayRef。如果有任何地方需要将本地影子复制回实例,编译器会在这些地方抱怨。我希望有一天 JIT 会完成这种代码不变的移动(Java 6/7 目前似乎很少,如果有的话)。
  • 我最初投了反对票,因为答案太简单了......即“计算机说不”。我喜欢你编辑过的答案,并且已经投了赞成票。
【解决方案2】:

我经常看到这种情况。顺序一般是:

  1. 认为性能与编译器优化、big-O 等有关。

  2. 使用推荐的思想设计软件、大量的类、双向链表、带有向上、向下、向左和向右指针的树、哈希集、字典、调用其他属性的属性、调用的事件处理程序其他事件处理程序、XML 写入、解析、压缩和解压缩等。

  3. 由于所有这些数据结构都像 O(1) 并且编译器正在优化其胆量,因此应用程序应该是“高效”的,对吧?那么,那个小声音在说启动慢,关机慢,加载和卸载可以更快,为什么UI这么卡呢?

  4. 把它交给“性能专家”。幸运的是,那个人发现,所有这些事情都是按照推荐的方式完成的,但这就是为什么它正在全力以赴。它做所有这些事情是因为它是推荐的做事方式,不是因为需要它

  5. 运气好的话,有机会重新设计其中的一些东西,使其变得简单,并逐渐消除“瓶颈”。我说“运气好”,因为这通常是不可能的,因此开发依赖于下一代更快的处理器来消除痛苦。

这在每种语言中都会发生,但在 Java、C#、C++ 中更是如此,在这些语言中,抽象已经达到了极致。因此,请务必了解最佳实践,但也要了解简单软件是什么。通常它包括为真正需要它们的情况保存这些最佳实践。

【讨论】:

  • +1 因为你们俩都没有喝 Java Cool Aid [TM]。
  • 最佳实践不一定会鼓励不良代码。
  • @Jay:必须——不。通常 - 是的。一个真正经验丰富的程序员需要不要过度使用它们。这就是为什么我说“为真正需要它们的情况保存这些最佳实践”。嘿 - 我曾经做过同样的事情,直到我学到了一些关于性能的东西 - stackoverflow.com/questions/926266/…
【解决方案3】:

哪些代码优化有意义 从编译器的角度来看?

编译器无法推理的所有问题,因为编译器非常愚蠢,而 Java 没有“契约式设计”(因此,这无法帮助愚蠢的编译器推理您的代码)。

例如,如果您正在处理数据并使用 int[] 或 long[] 数组,您可能知道一些关于您的数据IMPOSSIBLE供编译器计算的信息,您可以使用低级位打包/压缩,以提高代码中该部分引用的局部性。

去过那里,做到了,看到了巨大的加速。 “超级智能编译器”就这么多。

这只是一个例子。这样的案例比比皆是。

请记住,编译器真的很愚蠢:它无法知道 if ( Math.abs(42) > 0 ) 将始终返回 true。

这应该给那些认为那些编译器是“智能”的人一些启发(如果 Java 有 DbC,情况会有所不同,但事实并非如此)。

有哪些最佳做法: vmargs,堆和其他东西传递给 JVM 进行初始化。我如何能 在这里得到正确的值?在那儿 任何公式还是尝试和错误?

真正的答案是:不应该。可悲的是,由于 Java 方面的严重失败,这种情况是如此可悲,以至于需要这种低级的黑客攻击。哦,还有一个“小”细节:玩 VM 微调只适用于服务器端应用程序。它不适用于桌面应用。

任何曾在成百上千台机器上、在各种操作系统上安装过 Java 桌面应用程序的人都非常清楚问题是什么:完全 GC 暂停使您的应用程序看起来像是损坏了。想到 OS X 10.4 上的 Apple VM,因为它特别可靠,但 所有 JVM 都会遇到这个问题。

更糟糕的是:当您的应用程序要在成百上千的不同配置上运行时,不可能跨不同的操作系统/虚拟机/内存配置“微调”GC 的参数。

任何对此提出异议的人:请告诉我您如何“微调”您的应用程序,因为您知道它将在装有 20 GB 内存的八核 Mac 上运行(我有这样设置的用户)和旧的具有 768 MB 内存的 OS X 10.4 PowerBook。请问?

但这还不错:首先,您不应该关注像 GC“微调”这样的超低级细节。暗示这一点的事实证明了 Java 存在重大问题的一个领域。

Java 爱好者会一直说“GC 超级快,创建对象很便宜”,而这显然是错误的。 Trove 的 TIntIntHashMap 绕圈运行 HashMap 是有原因的。

还有一个原因是,在每个新的 JVM 版本中,您都会收到无数的发行说明,解释为什么 -XXGCHyperSteroidMultiTopNotch 提供比每个酷 Java 程序员都必须知道的最后一个“大 JVM 参数”更好的性能:也许 JVM 在 GC 方面并没有那么

那么回答您的问题:您如何加快 Java 程序的速度?简单,就像 Trove 的人所做的那样:停止不必要地创建大量对象并停止不必要地自动(取消)装箱原语,因为它们杀死您的应用程序的性能。

A TIntIntHashMap OWNS 默认的 HashMap 原因:出于同样的原因,我的应用现在比以前快很多

我不再相信诸如“对象创建不需要任何成本”“GC 是超级优化的,不用担心”这样的废话

我正在使用 Java 来处理数据(我知道,我有点疯狂),让我的应用程序更快的一件事是不要再相信围绕“廉价对象创建”和“惊人的快速 GC”的所有宣传”。

事实是:不要尝试微调您的 GC 设置,而是首先停止创建那么多垃圾。这可以这样说:如果更改 GC 设置从根本上改变了您的应用程序的运行方式,那么可能是时候怀疑您创建的所有不必要的垃圾对象是否真的需要。

哦,你知道吗,我打赌我们会看到越来越多的发行说明解释为什么 Java 版本 x.y.z 的 GC 比版本 x.y.z-1 的 GC 快;)

【讨论】:

  • ++ 追求现实主义和激情。我看到很多代码都在假设它“便宜”的情况下制造和放弃。
  • “请记住,编译器真的很愚蠢:它无法知道 if (Math.abs(42) > 0 ) 将始终返回 true。” - 实际上它可以知道那。这可能不是因为在整个应用程序中尝试这种优化的通用版本的成本/收益分析很差。
【解决方案4】:

一般情况下,你需要对 Java 进行两种性能优化:

  • 算法优化。选择一种行为符合您需要的算法。例如,一个简单的算法可能对小数据集表现最好,但准备更智能算法的开销可能首先会为更大的数据集带来回报。

  • 瓶颈识别。在这里你需要熟悉一个能告诉你问题是什么的分析器(人类总是猜错)——内存泄漏?,慢方法?等等... 一个好的开始是 VisualVM,它可以附加到正在运行的程序,并且在最新的 Sun JDK 中可用。知道问题后,就可以解决。

【讨论】:

    【解决方案5】:

    当今的 JVM 在性能方面出奇地强大。在几乎所有情况下,您可以应用的任何微优化都只会对性能产生非常小的影响。如果您看一下典型的语言结构(例如 FOR 与 WHILE)如何转换为字节码,这很容易理解——它们几乎无法区分。 使方法/变量最终化对一个像样的 JIT'd JVM 的性能绝对没有影响。 JIT 将跟踪哪些方法是真正多态的,并在可能的情况下优化动态调度。静态方法仍然可以更快,因为它们没有 this-reference = 少一个局部变量(同时限制了它们的应用)。最有效的微优化并不是 Java 特有的,例如具有大量条件语句的代码可能会由于处理器的分支错误预测而变得非常慢。有时条件语句可以被其他顺序代码流结构替换(通常以可读性为代价),从而减少错误预测的分支的数量(这适用于以某种方式编译为本机代码的所有语言)。

    请注意,分析器往往会夸大花在简短且经常调用的方法上的时间。这是因为分析器需要检测代码以跟踪调用 - 这可能会干扰 JIT 内联这些方法的能力(并且检测开销变得明显大于实际执行方法主体所花费的时间)。手动内联,虽然在探查器下显然非常性能提升,但在大多数情况下,在“现实世界”条件下没有效果。不要仅仅依赖分析器的结果,验证您所做的优化是否在实际运行时条件下也有真正的影响。

    只有通过减少已完成工作量的更改、对缓存更友好的数据布局或卓越的算法,才能预期显着的性能提升。 Java 部分限制了缓存友好数据布局的可能性,因为您无法控制构成数据结构的部分(数组/对象)在内存中的相对位置。尽管如此,仍有很多机会为工作选择正确的数据结构可以产生巨大的影响(例如 ArrayList 与 LinkedList)。

    您几乎无法帮助垃圾收集器。然而,值得注意的一点是,虽然 Java 中的对象分配非常非常快,但仍然存在对象初始化的成本(这主要在您的控制之下)。创建大量(短期)对象的应用程序性能不佳更可能归因于缓存利用率低下,而不是垃圾收集器的工作。

    不同的应用程序类型需要不同的优化策略 - 因此,在询问具体优化之前,请先了解您的应用程序真正花费时间的地方。

    【讨论】:

      【解决方案6】:

      如果您的应用程序遇到性能问题,您应该认真考虑尝试一些分析(例如:hprof)以查看问题是否本质上是算法问题,并检查 GC 性能日志记录(例如:-verbose:gc)看看您是否可以从调整 JVM GC 选项中受益。

      【讨论】:

        【解决方案7】:

        值得注意的是,编译器几乎没有进行任何优化,JVM 也没有在字节码级别进行优化。大多数优化都是由 JVM 中的 JIT 执行的,它优化了如何将代码转换为本机代码。

        优化代码的最佳方法是使用分析器,当您为其提供真实数据集时,该分析器可测量应用程序使用了多少时间和资源。如果没有这些信息,您只是在猜测,您可以更改很多代码,但实际上并不重要,并发现您在此过程中添加了错误。

        许多人得出的结论是,它永远不值得优化您的代码,甚至会适得其反,因为它会浪费时间并引入错误,我会说 95% 以上的代码都是如此。但是,使用分析器,您可以测量关键代码片段并优化

        【讨论】:

          【解决方案8】:

          很难彻底回答这个问题,因为你甚至没有提到你在谈论什么样的项目。它是桌面应用程序吗?服务器端应用程序?

          桌面应用程序偏爱应用程序启动时间,因此 HotSpot 客户端 VM 是一个好的开始。客户端应用程序不一定总是需要所有的堆空间,因此在起始堆和最大堆之间取得良好的平衡是有用的。 (比如,也许 -Xms128m -Xmx512m)

          服务器应用程序有利于整体吞吐量,这是 HotSpot 服务器 VM 的优化目标。您应该始终在服务器应用程序上分配相同的最小和最大堆大小。在垃圾收集期间必须使用 malloc() 和 free() 会增加系统级别的成本。使用类似 -Xms1024m -Xmx1024m 的东西。

          还有几种不同的垃圾收集器,它们针对不同的应用程序类型进行了调整。

          如果您想了解有关 Java 6 中垃圾收集器和其他性能相关项目的更多信息,请阅读 Java SE 6 Performance White Paper

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-04-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-04-04
            • 2011-03-17
            • 1970-01-01
            相关资源
            最近更新 更多