【问题标题】:Java 11 string concatenation performance Vs java 8Java 11 字符串连接性能与 Java 8
【发布时间】:2019-03-28 11:02:27
【问题描述】:

有谁知道为什么我在 Java 8 和 Java 11 上运行此代码时会获得如此不同的性能?

在不使用任何运行时标志的情况下,与 Java 8 相比,此代码在 Java 11 下的运行速度明显慢得多。

import java.util.Date;

public class PerformanceExperiment {
    public static volatile String s = "";

    public static void main(String[] args)
    {                         
        System.out.println("Starting performance test");
        String s1 = "STRING ONE";
        String s2 = "STRING TWO";
        long now1 = (new Date()).getTime();
        for (long i = 0; i < 1_000_000_00; i++)
        {
            s = "abc " + s1 + " def " + s2;
        }
        long now2 = (new Date()).getTime();
        System.out.println("initial block took " + (now2 - now1) + "ms");
        for (long i = 0; i < 4_000_000_00; i++)
        {
            s = "abc " + s1 + " def " + s2;
        }
        long now3 = (new Date()).getTime();
        System.out.println("Main block took " + (now3 - now2) + "ms");
    }
}

我尝试了许多命令行标志,但没有找到任何与 Java 8 性能相匹配的东西。

我只在 Windows 上对此进行了测试,因此它在其他操作系统上的行为可能会有所不同。

【问题讨论】:

  • 问题真的存在吗??你的数据是什么
  • 几乎只使用 Java 十多年了,我刚刚了解到您可以在 int 文字中添加下划线... mindblast
  • @Alex 你怎么不提供你的输出?你说“明显慢”但什么是重要的?程序有输出吗?
  • @Alex 你用过 JMH。请使用它,它会预热你的 JVM 等等。

标签: java string performance concatenation


【解决方案1】:

TL;DR:需要更好的基准测试、更好的设置以控制版本之间的差异等。使用 JMH 可以轻松解决大多数基准测试问题。当前的测试行为似乎可以通过有问题的基准测试方法和默认 GC 的变化来解释。

考虑一下:

public class PerformanceExperiment {
    public static volatile String s = "";

    public static void main(String[] args) {
        for (int c = 0; c < 5; c++) {
            test();
        }
    }

    public static void test() {
        String s1 = "STRING ONE";
        String s2 = "STRING TWO";
        long time1 = System.currentTimeMillis();
        for (long i = 0; i < 4_000_000_00; i++) {
            s = "abc " + s1 + " def " + s2;
        }
        long time2 = System.currentTimeMillis();
        System.out.println("Main block took " + (time2 - time1) + "ms");
    }
}

首先,它使用更方便的计时。然后,它测量相同字节码块,而原始测试预热“初始”,然后继续测量绝对冷的。

然后,JIT 编译会命中该方法,并且您想再次重新输入该方法以运行优化的代码,否则您正在运行中间的“堆栈上替换”代码 - 您可以使用外部调用test 的迭代。最重要的是,您希望多次输入以捕获最优化的版本。

而且,由于测试分配了很多,你想确定堆大小。

所以,这里:

$ ~/Install/jdk8u191-rh/bin/javac PerformanceExperiment.java
$ ~/Install/jdk8u191-rh/bin/java -Xms2g -Xmx2g PerformanceExperiment
Main block took 10024ms
Main block took 9768ms
Main block took 7249ms
Main block took 7235ms
Main block took 7205ms

...这是相同字节码上的 11.0.2:

$ ~/Install/jdk11.0.2/bin/java -Xms2g -Xmx2g PerformanceExperiment
Main block took 9775ms
Main block took 10825ms
Main block took 8635ms
Main block took 8616ms
Main block took 8622ms

...这里是匹配 GC 的 11.0.2(9+ 将默认值更改为 G1 和 JEP 248):

$ ~/Install/jdk11.0.2/bin/java -Xms2g -Xmx2g -XX:+UseParallelGC PerformanceExperiment
Main block took 9281ms
Main block took 9129ms
Main block took 6725ms
Main block took 6688ms
Main block took 6684ms

最重要的是,每次微小的迭代都有volatile 存储,这会花费很多并且可能会扭曲基准。

还有 interaction 与 indified string concat (JEP 280)、线程本地握手 (JEP 312) 和其他 VM 修复,但您可能只会在编译超过 target=8 时看到,这超出了这个练习。

【讨论】:

  • 我同意你提出的所有观点。然而,在我看来,一个相当简单和小的代码示例可能需要更长的时间才能在新的开箱即用的 JDK 中运行,而不使用任何标志。
  • 然后呢? JDK 确实不同,它们做出不同的权衡,从而导致可量化的不同性能行为。除非您可以证明特定的代码示例 比其他所有内容都重要得多,否则这里没有问题。换句话说,你的测试肯定会衡量一些东西,但真正的问题是它是否衡量了你需要的东西。
  • @alex 说得有道理……我们在机器人 Java 应用程序中遇到了这种行为变化。前几次调用字符串 concat (在我们的日志代码中)所需的额外秒数让我们在比赛中失望了。多亏了这个帖子,我们终于找到了根本原因,但为时已晚。
  • @Billy 永远不会在“生产”之前更改设置?毕竟,这是一条黄金法则。
【解决方案2】:

我将您的应用修改为

  1. 使用System.nanoTime() 而不是new Date() 以获得更高的精度(有关更多信息,请参阅此答案:https://stackoverflow.com/a/1776053/963076)。
  2. 使用 Netbeans 分析器。
  3. 循环 10 次迭代

将 Netbeans 8.2 与 JDK 8 v181 一起使用:

Starting performance test 0
initial block took 3147ms
Main block took 9469ms
Starting performance test 1
initial block took 2398ms
Main block took 9601ms
Starting performance test 2
initial block took 2463ms
Main block took 9671ms
Starting performance test 3
initial block took 2464ms
Main block took 9565ms
Starting performance test 4
initial block took 2410ms
Main block took 9672ms
Starting performance test 5
initial block took 2418ms
Main block took 9598ms
Starting performance test 6
initial block took 2384ms
Main block took 9733ms
Starting performance test 7
initial block took 2402ms
Main block took 9610ms
Starting performance test 8
initial block took 2509ms
Main block took 11222ms
Starting performance test 9
initial block took 2455ms
Main block took 10661ms

分析器显示了这个遥测数据:

使用 JDK 11.0.2 的 Netbeans 10.0:

Starting performance test 0
initial block took 3760ms
Main block took 15056ms
Starting performance test 1
initial block took 3734ms
Main block took 14602ms
Starting performance test 2
initial block took 3615ms
Main block took 14762ms
Starting performance test 3
initial block took 3748ms
Main block took 14534ms
Starting performance test 4
initial block took 3628ms
Main block took 14759ms
Starting performance test 5
initial block took 3625ms
Main block took 14959ms
Starting performance test 6
initial block took 3987ms
Main block took 14967ms
Starting performance test 7
initial block took 3803ms
Main block took 14701ms
Starting performance test 8
initial block took 3599ms
Main block took 14762ms
Starting performance test 9
initial block took 3627ms
Main block took 14434ms

我的结论:JDK 11 在提高内存效率方面做得更多。请注意,在 JDK11 中,垃圾收集器中“幸存的世代”的数量要少得多,而且内存使用量在使用率和波动性方面也明显减少。权衡似乎是在速度上,但速度上的差异小于内存使用的差异。

【讨论】:

    【解决方案3】:
    猜你喜欢
    • 1970-01-01
    • 2019-07-24
    • 1970-01-01
    • 2020-03-28
    • 2011-04-02
    • 2011-02-15
    • 2014-07-24
    • 1970-01-01
    相关资源
    最近更新 更多