【问题标题】:Java performance tipsJava 性能提示
【发布时间】:2010-10-30 15:14:51
【问题描述】:

我有一个从 C 移植到 Java 的程序。这两个应用程序都使用快速排序来排序一些分区数据(基因组坐标)。

Java 版本运行速度很快,但我想让它更接近 C 版本。我使用的是 Sun JDK v6u14。

显然我无法与 C 应用程序相提并论,但我想了解我可以做些什么来尽可能地提高性能(在环境限制内)。

我可以做哪些事情来测试应用程序不同部分的性能、内存使用情况等?具体我该怎么做?

此外,我可以(通常)实施哪些技巧来更改我的类和变量的属性和组织,减少内存使用并提高速度?

编辑:我正在使用 Eclipse,显然更喜欢任何第三方工具的免费选项。谢谢!

【问题讨论】:

  • 您是否针对 Java 版本对 C 版本进行了基准测试。使用 JIT 技术,JVM 在某些情况下实际上可以胜过本机代码,具体取决于 C 编译器实际生成的机器代码。
  • 根据数据的性质以及您在快速排序算法中选择基准的方式,您可能需要考虑合并排序,因为它最坏的情况是 O(n log n)
  • 尝试使用内置的快速排序,看看它与您的实现相比如何。你可能会感到惊讶:)
  • 一些可能也与 Java 相关的性能改进提示:stackoverflow.com/questions/32581644

标签: java profiling performance


【解决方案1】:

不要试图智取 jvm。

特别是:

  • 不要试图避免创建对象 为了性能

  • 使用不可变对象 适用。

  • 使用对象的范围 正确,以便 GC 可以执行它的 工作。

  • 使用原语 原语(例如,不可为空的 int 与可为空的整数相比)

  • 使用内置算法和数据结构

  • 处理并发时使用 java.util.concurrent 包。

  • 正确性高于性能。先把它做对,然后测量,然后用分析器测量,然后优化。

【讨论】:

  • 虽然您的许多建议可能是正确的,但我会大胆地不同意第一个建议。在我的许多项目中,减少对象创建是优化中最关键的部分。当然,保存 10 个大对象是没有用的,但通常你最终会得到数百万个小对象,不创建它们很重要。
  • Andreas 基本上是正确的,现代 VM 中的对象分配非常快(例如比 C++ 快得多),并且假设它们的寿命不长,它们将在次要集合中被清理。话虽如此,请介绍一下,再介绍一下,只做你有证据的事情,而不仅仅是因为有人这么说。
  • 超越虚拟机意味着尽量不尝试提高性能,例如避免创建对象。将这些类型的优化留给编译器和 VM。之后,也许连接一个分析器,例如 VisualVM/JProfiler,以查看代码的哪些部分对程序性能的影响最大,然后集中精力改进它们。
  • 第一句话应该是“超越虚拟机意味着尽量不提高性能,例如避免创建对象”。
  • @Brian:在 Java 1.4 中,为一个对象分配内存只需要不到 10 条处理器指令。可移动分代 GC 的神奇之处在于空闲内存始终是连续的,分配 10 个字节只是返回当前空闲指针并将其递增 10。相反,重用持有对可能较新对象的引用的旧对象会影响性能(可能会强制内存移动) .那就是“试图超越虚拟机”。根据您的对象定义和您使用的代码,您可以提高或降低性能。
【解决方案2】:

显然,个人资料个人资料。对于 Eclipse,有 TPTP。这是一篇关于TPTP plugin for Eclipse 的文章。 Netbeans 有自己的profilerjvisualvm 很适合作为独立工具。 (整个 dev.java.net 服务器目前似乎已关闭,但它是一个非常活跃的项目。)

首先要做的是使用库排序例程,Collections.sort;这将要求您的数据对象为Comparable。这可能足够快,并且肯定会提供良好的基线。

一般提示:

  • 避免不需要的锁(您的 JVM 可能已经优化了这些)
  • 使用StringBuilder(不是StringBuffer,因为我刚才提到的那个锁)而不是连接String对象
  • 尽你所能final;如果可能的话,让你的类完全不可变
  • 如果您没有在循环中更改变量的值,请尝试将其吊出并查看是否有影响(JVM 可能已经为您完成了此操作)
  • 尝试使用ArrayList(甚至是数组),这样您正在访问的内存是连续的,而不是像LinkedList 那样可能是碎片化的
  • 快速排序可以并行化;考虑这样做(见quicksort parallelization
  • 尽可能减少数据的可见性和生存时间(但不要扭曲您的算法,除非分析表明这是一个巨大的胜利)

【讨论】:

  • 当逃逸分析到位并且 100% 正确工作时,StringBuilder 具有与 StringBuffer 相同的性能特征。 j6u14下还没试过。所以你不应该太担心这个。
  • 普通字符串的连接是通过StringBuilder实现的,并且在很多情况下进行了优化。这种微优化使一些人在过去使用 StringBuffer-s 只是为了意识到现代虚拟机的普通字符串连接比我们手工定制的要快……现在谁会将那些 StringBuffer 重构为 StringBuilder?这是试图超越编译器/虚拟机的例子之一。
  • 是的,从字符串连接切换到使用 StringBuilder 在我正在渲染的几个 JSP 中带来了巨大的性能提升。好点。
  • @Andreas:当已经有一个明确表示该意图的类时,希望 JVM 能够弄清楚您的意思是没有用的(我不需要“线程安全”附加)。 StringBuffer 无论如何都不会处理真正线程安全的程序需要的大量锁定(主要是排序问题)。 @dribeas:我意识到这是理论上的,但是任何数量的事情都可能导致 JVM 不执行该优化。此外,StringBuffer -> StringBuilder 在任何有效的情况下都是微不足道的重构。
【解决方案3】:

使用分析器:

使用您的提供商提供的最新版本的 JVM。顺便说一句,Sun 的 Java 6 更新 14 确实带来了performance improvements

测量您的 GC 吞吐量和pick the best garbage collector for your workload

【讨论】:

  • visualvm 的链接似乎已失效。还在维护吗?
  • 是的,最后一次 Java 更新 - Java 6 update 14 - 对其进行了改进。它非常有活力。
  • 目前所有 dev.java.net 似乎都关闭了,所以这就是链接关闭的原因。
【解决方案4】:

不要过早优化。

衡量性能,然后优化。

尽可能使用最终变量。它不仅允许 JVM 优化更多,也让你的 代码更易于阅读和维护。

如果您使对象不可变,则不必克隆它们。

首先更改算法,然后更改实现进行优化。

有时您需要使用旧式技术,例如循环展开或缓存预先计算的值。记住它们,即使它们看起来不好看,它们也很有用。

【讨论】:

    【解决方案5】:

    jvisualvm 现在附带 JDK 6 - 这就是上面引用的链接不起作用的原因。只需键入“jvisualvm ”,其中 是您要跟踪的进程的 ID。您将看到堆是如何被使用的,但您不会看到它被什么填充了。

    如果是长时间运行的进程,可以在运行时开启 -server 选项。有很多调整选项可供您使用;这只是一个。

    【讨论】:

      【解决方案6】:

      还可以尝试调整 VM 的运行时参数 - 例如,最新版本的 VM 包含以下标志,可以在某些情况下提高性能。

      -XX:+DoEscapeAnalysis 
      

      【讨论】:

        【解决方案7】:

        第一个警告 - 在开始任何优化工作之前,请确保您已完成适当的分析或基准测试。结果通常会启发您,并且几乎总能避免您在优化无关紧要的内容时浪费大量精力。

        假设你确实需要它,那么你可以在 Java 中获得与 C 相当的性能,但这需要一些努力。您需要知道 JVM 在哪里做“额外工作”并避免这些。

        特别是:

        • 避免不必要的对象创建。虽然 JVM 堆和 GC 非常快速和高效(可能是世界上最好的,而且几乎可以肯定比任何你可以在 C 中自己滚动的东西都要好),但它仍然是堆分配,并且会通过在第一次避免堆的情况下被击败放置(堆栈或寄存器分配)
        • 避免盒装基元。您想使用double 而不是Double
        • 对任何大块数据使用原始数组。 Java 原始数组基本上与 C/C++ 数组一样快(它们确实有额外的边界检查,但通常无关紧要)
        • 避免同步 - Java 线程相当不错,但它仍然是您可能不需要的开销。为每个线程提供它自己的数据来处理。
        • 利用并发 - Java 的并发支持非常好。您不妨使用所有核心!这是一个很大的话题,但有很多好书/教程可供使用。
        • 如果您有一些非常具体的要求,例如,对于某些类型的数据使用专门的集合类支持一些专门的排序/搜索算法。您可能需要自己动手,但也有一些具有高性能集合类的优秀库可以满足您的需求 - 参见例如Javoltion
        • 避免大类继承 - 这是性能代码中的设计味道。每一层抽象都在消耗你的开销。非常快的 Java 代码通常最终看起来很像 C....
        • 使用静态方法 - JIT 可以非常好地优化这些方法。它通常会内联它们。
        • 使用最终的具体类 - 同样,JIT 可以通过避免虚函数调用来很好地优化这些。
        • 生成您自己的字节码 - 如果所有其他方法都失败了,如果您希望 JVM 的绝对最大性能,这可能是一个可行的选择。如果您需要编译自己的 DSL,则特别有用。使用 ASM 之类的内容。

        【讨论】:

          【解决方案8】:

          如果您的算法占用大量 CPU,您可能需要考虑利用并行化。您也许可以在多个线程中排序并稍后将结果合并回来。

          然而,这不是一个掉以轻心的决定,因为编写并发代码很困难。

          【讨论】:

            【解决方案9】:

            您不能使用 Java 库中包含的排序函数吗?

            你至少可以看看这两个排序功能之间的速度差异。

            【讨论】:

            • 我使用的比较器是定制的,用于处理基因组和位置数据结构的排序。
            • @Alex 如果你让你的数据对象实现Comparable你仍然可以使用库排序。
            • @Hank:为什么@Alex 不能使用带有 Comparator 的重载排序方法?
            • @Hemal 使用自然排序时,代码更简洁:无需创建比较器,传递的参数更少。当然,如果 Alex 的排序标准作为自然顺序没有意义,那么 Comparator 版本就是要走的路。
            【解决方案10】:

            从方法上讲,您必须分析应用程序,然后了解程序的哪些组件是时间和内存密集型的:然后仔细查看这些组件,以提高它们的性能(请参阅Amdahl's law) .

            从纯技术 POV 来看,您可以使用一些 java-to-nativecode 编译器,例如 Excelsior 的 jet,但我必须注意最近的 JVM 非常快,因此 VM 应该不会产生显着影响。

            【讨论】:

            • 好的,但是我实际上会使用什么工具来达到这个目的?
            • 选择一个分析器并使用它。我用过jprofiler:很好,但是要花钱。
            • 我也用过eclipse的TPTP,但与jprofiler提供给你的相比,它的能力很差。
            • TPTP 很难正确设置。我不止一次尝试将它与 Eclipse 3.4.x 一起使用,但都失败了。 JProfiler“正常工作”。
            【解决方案11】:

            您的排序代码是否只执行一次,例如在仅排序或多次排序的命令行实用程序中,例如响应某些用户输入进行排序的 web 应用?

            代码执行几次后性能可能会显着提高,因为如果 HotSpot VM 确定您的代码是热点,它可能会积极优化。

            与 C/C++ 相比,这是一个很大的优势。

            VM 在运行时会优化经常使用的代码,而且它做得很好。因此,性能实际上可以超越 C/C++。真的。 ;)

            不过,您的自定义比较器可能是优化的地方。

            尝试先检查便宜的东西(例如 int 比较),然后再检查更昂贵的东西(例如字符串比较)。我不确定这些提示是否适用,因为我不认识您的比较器。

            使用 Collections.sort(list,comparator) 或 Arrays.sort(array,comparator)。数组变体会更快一些,请参阅相应的文档。

            正如 Andreas 之前所说:不要试图智取 VM。

            【讨论】:

              【解决方案12】:

              除了代码的微优化之外,也许还有其他途径可以提高性能。用不同的算法来实现你想让你的程序做的事情怎么样?可能是不同的数据结构?

              或者用一些磁盘/内存空间换取速度,或者如果您可以在加载程序期间预先放弃一些时间,您可以预先计算查找表而不是进行计算 - 这样,处理速度很快。即,对其他可用资源进行一些权衡。

              【讨论】:

                【解决方案13】:

                Here's what I would do, in any language. 如果样本显示您的排序比较例程在大部分时间处于活动状态,您可能会找到一种简化它的方法。但也许时间正在别处。先诊断,看看有什么问题,然后再修复任何东西。很有可能,如果你修复了最大的问题,那么其他的东西就会成为最大的问题,依此类推,直到你真正获得了相当不错的加速。

                【讨论】:

                  【解决方案14】:

                  分析和调整您的 java 程序和主机。大多数代码遵循 80/20 规则。那是 20% 的代码 80% 的时间,所以找到 20% 并尽可能快地完成它。例如,文章 Tuning Java Servers (http://www.infoq.com/articles/Tuning-Java-Servers) 提供了从命令行向下钻取的描述,然后使用 Java Flight recorder、Eclipse Memory Analyser 和 JProfiler 等工具隔离问题。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-08-05
                    • 1970-01-01
                    • 2012-03-31
                    • 1970-01-01
                    • 2011-04-09
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多