【问题标题】:HashMap performance Java 9 25% less than Java 8?HashMap 性能 Java 9 比 Java 8 低 25%?
【发布时间】:2018-04-09 20:41:37
【问题描述】:

注意:这不是关于性能问题。我只观察到我无法解释/理解的性能差异。

在对一些针对 Java 9 的新开发代码进行基准测试时,我发现了一些奇怪的东西。一个(非常)简单的HashMap 基准测试(带有 5 个键)显示 Java 9 比 Java 8 慢得多。这可以解释还是我的(基准)代码完全错误?

代码:

@Fork(
    jvmArgsAppend = {"-Xmx512M", "-disablesystemassertions"}
)
public class JsonBenchmark {

    @State(Scope.Thread)
    public static class Data {

        final static Locale RUSSIAN = new Locale("ru");
        final static Locale DUTCH = new Locale("nl");

        final Map<Locale, String> hashmap = new HashMap<>();

        public Data() {
            hashmap.put(Locale.ENGLISH, "Flat flashing adjustable for flat angled roof with swivel");
            hashmap.put(Locale.FRENCH, "Solin pour toit plat inclinée");
            hashmap.put(Locale.GERMAN, "Flachdachkragen Flach Schrägdach");
            hashmap.put(DUTCH, "Plakplaat vlak/hellend dak inclusief glijschaal");
            hashmap.put(RUSSIAN, "Проход через плоскую кровлю регулир. для накл. кровли");
        }

    }

    @Benchmark
    public int bmHashMap(JsonBenchmark.Data data) {
        final Map<Locale, String> m = data.hashmap;
        int sum = 0;
        sum += m.get(Data.RUSSIAN).length();
        sum += m.get(Locale.FRENCH).length();
        sum += m.get(Data.DUTCH).length();
        sum += m.get(Locale.ENGLISH).length();
        sum += m.get(Locale.GERMAN).length();
        return sum;
    }    

}

结果:

  • Java 8_151:JsonBenchmark.bmHashMap thrpt 40 47948546.439 ± 560763.711 ops/s
  • Java 9_181:JsonBenchmark.bmHashMap thrpt 40 34962904.479 ± 276045.691 操作/秒(-/- 27%!)

更新

感谢您的回答和伟大的 cmets。

  1. @Holger 的建议。我的第一反应是:这一定是解释。但是,如果我只对String#length() 函数进行基准测试,性能上并没有显着差异。而且,当我只对 HashMap#get() 方法进行基准测试时(如 @Eugene 建议的那样),仍然存在大约 10 - 12 % 的差异。

  2. @Eugene 的建议。我更改了参数(更多的热身迭代,更多的内存),但我无法重现您的结果。但是,我将堆增加到 4G。但这并不能解释差异,不是吗?

  3. @Alan Bateman 的建议。是的,这提高了性能!不过还是相差20%左右。

【问题讨论】:

    标签: performance-testing java-9 jmh


    【解决方案1】:

    您正在测试的不仅仅是HashMap。您不仅在调用HashMap.get,而且在隐式调用Locale.hashCodeLocale.equals。此外,您正在致电String.length

    现在,所有四种方法都可能改变了它们的性能特征,因此您需要更多的测试来推断哪些方法表现出不同的性能。

    但最热门的候选人是String.length。在 Java 9 中,String 类不再使用 char[] 数组,而是使用 byte[] 数组,对拉丁文 1 字符串进行编码,每个字符仅使用一个字节,从而显着减少了典型应用程序的内存占用。然而,这意味着长度不再总是与数组长度相同。所以这个操作的复杂度已经改变了。

    但请记住,您的结果在微基准测试中相差约 77 ns。这不足以估计对实际应用程序的影响……

    【讨论】:

    • 虽然这可能有点过分,但我不介意你完全否认这一点。但是(作为 jmh 的新手)如果可能的话,您能否说明两个版本中 .length 的复杂性比较。很高兴在答案和统计中看到这一点,同时理解。查找 here 看看我是否能找到一些东西,但大部分都超出了我的想象。
    • 好答案。另一件要提的是 -XX:-CompactStrings 以查看不使用紧凑字符串时的性能差异。
    • @nullpointer:旧实现类似于array.length,新实现类似于arrays.length&gt;&gt;coder,其中 coder 为 0 或 1,具体取决于字符串。实际应用程序的影响取决于实际调用 length() 的频率以及 latin1 字符串与其他字符串之间的比率。对于 latin1 字符串,其他一些字符串操作可能会更快(需要挖掘的字节更少),这可能会超过补偿。
    • @Holger 我喜欢 Aleksey(从事此工作的同一个人和 jol)说添加 coder 字段不会使 String 类变得更大,因为它的空间已经存在8 字节对齐。
    • @Eugene:它的字段仍然比当时少,当时它有一个 offsetlength 字段(在 Java7 之前,update6)。
    【解决方案2】:

    我暗示这是关于jmh 的设置,更多的是关于HashMap。如前所述,您在此处测量的很多不仅仅是HashMap::get。但即便如此,我还是怀疑 java-9 会慢得多,所以我测量了自己(最新的 jmh 来自源代码,java-8 和 9)。

    我没有更改您的代码 - 只是添加了更多的堆 (10GB) 和更多的热身,从而减少了您在 ± 之后看到的“错误”

    使用 java-8:

    Benchmark            Mode  Cnt   Score   Error  Units
    SOExample.bmHashMap  avgt   25  22.059 ± 0.276  ns/op
    

    使用 java-9:

    Benchmark            Mode  Cnt   Score   Error  Units
    SOExample.bmHashMap  avgt   25  23.954 ± 0.383  ns/op
    

    如您所见,结果几乎没有明显差异(毕竟这些都是纳秒)。此外,如果您真的只想测试HashMap::get,那么您的方法可以简单地返回它的调用,如下所示:

    @Benchmark
    @Fork(5)
    public int bmHashMap(SOExample.Data data) {
        return data.hashmap.get(data.key); // where key is a random generated possible key
    }
    

    【讨论】:

      猜你喜欢
      • 2018-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-31
      • 2017-10-10
      相关资源
      最近更新 更多