Java 中 10 大简单的性能优化

JAVA 7 ForkJoinPool和 Java 8 的并行Stream有助于并行化东西,这在您将 Java 程序部署到多核处理器机器上时非常有用 。与跨网络上的不同机器进行扩展相比,这种并行性的优势在于您几乎可以完全消除延迟效应,因为所有内核都可以访问相同的内存 。但是不要被并行的效果所迷惑!记住以下两点:

  • 并行性会吞噬你的核心 。这对于批处理非常有用,但对于异步服务器(例如 HTTP)来说则是一场噩梦 。在过去的几十年里,我们使用单线程 servlet 模型是有充分理由的 。因此,并行性仅在扩大规模时才有帮助 。
  • 并行性对算法的Big O Notation没有影响 。如果您的算法是O(n log n),并且您让该算法在c内核上运行,您仍然会有一个O(n log n / c)算法,因为c在您的算法复杂性中是一个微不足道的常数 。您将节省挂钟时间,但不会降低复杂性!
当然,提高性能的最佳方法是降低算法复杂度 。杀手是实现O(1)或准O(1),当然,例如HashMap查找 。但这并不总是可能的,更不用说容易了 。如果你不能降低复杂性,如果你在真正重要的地方调整你的算法,如果你能找到正确的位置,你仍然可以获得很多性能 。假设以下算法的可视化表示:
Java 中 10 大简单的性能优化

文章插图
 
算法的整体复杂度是,或者如果我们要处理单个数量级 。但是,在分析此代码时,您可能会发现一个有趣的场景:O(N3)O(N x O x P)
  • 在您的开发框中,左分支 ( N -> M -> Heavy operation) 是您可以在分析器中看到的唯一分支,因为 和 的值O在P您的开发示例数据中很小 。
  • 然而,在生产中,正确的分支(N -> O -> P -> Easy operation或NOPE)确实造成了麻烦 。您的运营团队可能已经使用AppDynamics或DynaTrace或一些类似软件解决了这个问题 。
如果没有生产数据,您可能会很快得出结论并优化“繁重操作” 。你运送到生产环境,你的修复没有效果 。除了以下事实之外,没有优化的黄金法则:
  • 设计良好的应用程序更容易优化
  • 过早的优化不会解决任何性能问题,反而会使您的应用程序设计得不那么好,从而使优化变得更加困难
理论够了 。让我们假设您找到了正确的分支是问题所在 。很可能是一个非常简单的操作在生产中失败了,因为它被调用了很多次(如果N、O和P很大) 。请在不可避免算法的叶节点出现问题的情况下阅读本文 。这些优化不会帮助您扩展 。他们将帮助您暂时节省客户的时间,将整体算法的困难改进推迟到以后!O(N3) 以下是 Java 中最简单的 10 个性能优化:
1、使用StringBuilder这应该是几乎所有 Java 代码中的默认设置 。尽量避免使用+操作符 。当然,您可能会争辩说,它只是StringBuilder的语法糖,例如:
String x = "a" + args.length + "b";翻译为:
new java.lang.StringBuilder [16]dupldc <String "a"> [18]invokespecial java.lang.StringBuilder(java.lang.String) [20]aload_0 [args]arraylengthinvokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23]ldc <String "b"> [27]invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29]invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32]astore_1 [x]但是,如果稍后您需要使用可选部分修改您的字符串,会发生什么?
String x = "a" + args.length + "b"; if (args.length == 1)x = x + args[0];您现在将有第二个StringBuilder,这只是不必要地消耗您的堆内存,给您的 GC 施加压力 。改为这样写:
StringBuilder x = new StringBuilder("a");x.append(args.length);x.append("b"); if (args.length == 1);x.append(args[0]);在上面的示例中,如果您使用显式StringBuilder实例,或者您依赖 Java 编译器为您创建隐式实例,这可能完全无关紧要 。但请记住,我们在N.O.P.E.分支中 。我们浪费在像 GC 或分配 aStringBuilder的默认容量这样愚蠢的事情上的每个 CPU 周期,我们都在浪费N x O x P时间 。根据经验,始终使用 aStringBuilder而不是+运算符 。如果可以的话,如果你的构建更复杂,请保留StringBuilder多个方法的引用 。只有一个StringBuilder“遍历”你的整个 SQL AST(抽象语法树) 对于大声喊叫,如果您仍然有StringBuffer参考资料,请将它们替换为StringBuilder. 您几乎不需要同步正在创建的字符串 。
2、避免正则表达式正则表达式相对便宜和方便 。但是,如果您在 N.O.P.E. 分支中,那么它们就是您能做的最糟糕的事情 。如果您绝对必须在计算密集型代码部分使用正则表达式,至少缓存Pattern引用而不是一直重新编译它:


推荐阅读