【问题标题】:How do I explain Insert performance for HashMap in Java 8 and Java 7如何解释 Java 8 和 Java 7 中 HashMap 的插入性能
【发布时间】:2014-09-05 05:27:17
【问题描述】:

我正在经历 Java 8 HashMap 的改进,显示使用二叉树的搜索性能提高了大约 20%。

当时有一个想法是如何影响插入性能。所以我开始插入几百万条记录。下面是代码 sn-p 和不同的结果集。

import java.util.HashMap;
import java.util.Map;

public class MapWriter {

public static final int MAX_KEY = 1_000_000;

private Map<Double, Double> map = new HashMap<>(MAX_KEY);


public static void main(String[] args) {
    long startTime = System.currentTimeMillis();

    MapWriter writer = new MapWriter();

    for (int i = 0; i < MAX_KEY; i++) {
        double random = Math.random();
        writer.map.put(random, random);
    }

    long timeTaken = System.currentTimeMillis() - startTime;

    System.out.println("Total Time Taken = " + timeTaken);
    System.out.println("Map Size = " + writer.map.size());
}

}

以下是不同的结果:

  1. 1000 万次插入

    • Java 7

      • 总耗时 = 23145
    • Java 8

      • 总耗时 = 64964
  2. 200 万次插入

    • Java 7
      • 总耗时 = 6628
    • Java 8
      • 总耗时 = 8312
  3. 100 万次插入

    • Java 7
      • 总耗时 = 3577
    • Java 8
      • 总耗时 = 1212

结果表明,Java 8 的插入次数最多可达 100 万次。但是当你向上移动时,结果显示出相反的行为。

我该如何解释这种行为??????

更新:感谢大家的宝贵反馈。我需要了解有关基准测试的更多信息。我预先初始化了 Math.random 部分,结果对于 java 7 和 java 8 都是一样的。这是修改后的代码。如果从基准测试的角度来看代码看起来仍然很臭,请告诉我。

public class MapWriter {

public static int MAX_KEY = 1_000_000;


private Map<Double, Double> map = new HashMap<>(MAX_KEY);


public static void main(String[] args) {
    MAX_KEY = Integer.parseInt(args[0]);

    Double[] keys = new Double[MAX_KEY];

    for(int i = 0; i < MAX_KEY; i++) {
        keys[i] = Math.random();
    }

    MapWriter writer = new MapWriter();
    for (int i = 0; i < 100000; i++) {

        writer.map.put(keys[i], keys[i]);
    }

    writer.map = new HashMap<>(MAX_KEY);
    long startTime = System.nanoTime();


    for (int i = 0; i < MAX_KEY; i++) {
        double random = Math.random();
        writer.map.put(random, random);
    }

    long timeTaken = System.nanoTime() - startTime;

    System.out.println("Total Time Taken = " + timeTaken / 1000000);
    System.out.println("Map Size = " + writer.map.size());
}

}

【问题讨论】:

  • 我认为这种基准测试方法过于幼稚。您可能没有考虑的因素太多了。
  • 是否有证据表明 Java 8 的 HashMap 使用了二叉树?听起来不对。
  • @EJP 是的,请参阅HashMap.java,尤其是从第 143 行开始的实现说明。简而言之,如果哈希冲突导致任何 bin 中的人口过多,则该 bin 将从链表转换为二叉树。

标签: java hashmap java-8


【解决方案1】:

如果您编写自己的微基准测试而不是使用像 JMH 这样的专用工具,您应该考虑一些要点:

  • 使用System.nanoTime()。用于测量经过时间以免受 JVM 外部触发的时间变化的影响。
  • 不要使用Math.random()(除非你想对Math.random()进行基准测试)。使用全局随机生成器等全局资源会增加线程同步,这可能会影响您的代码。
  • 使用Random 实例作为随机输入的源,但使用常量seed 对其进行初始化,以确保您要比较的运行确实执行相同的操作
  • 在 JVM 中多次运行您想要进行基准测试的代码,以确保您不会测量初始化开销,例如类加载、首次内存分配、解释代码等。 p>

  • 通常,最好在测试运行期间运行分析器,以验证时间确实花在了您要进行基准测试的代码中

使用上面的最后一个项目符号,您的基准测试的主要问题很快就被发现了。你没有给 JVM 足够的初始内存(甚至可能最大内存太有限)并且只运行了一次代码。所以你在这里主要测量内存管理的影响。

请注意,Java 8 HashMap 需要稍微多一点的内存,并且在仅测量初始化成本时,稍微多一点的内存需求可能会产生很大的影响。

为两个 JVM 提供至少 1GB 的初始内存以进行 1000 万次插入,这导致在我的机器上使用 Java 8 大约需要 5 秒,而在 Java 7 上大约需要 6 秒,即使没有预热。距离你的 20 秒更不用说超过一分钟了。

最重要的是,在对不同结果的原因进行假设之前,您需要在不同的环境参数下进行更多的运行。当您在 32 位 JVM、-server-client(一个 64 位 JVM)上运行测试时,它们都具有不同的内存设置并获得一致的结果,所有这些都显示特定版本更快或更慢,那么您可能建议它是版本。但可能还有其他原因……

【讨论】:

  • Java 8 中引入的更改是仅用于查询性能还是其他方面?
  • @Sotirios Delimanolis:您是指哈希冲突的二叉搜索树?它首先是为了提高查找性能。但是,它使过去的某些其他尝试过时了,这可能会提高整体性能。例如。早期版本对每个哈希码都应用了改进算法,现在已经简化了。
  • 是的,我就是这个意思。谢谢。
【解决方案2】:

我认为您在运行基准测试时也在测试 Math.random() 和自动装箱。您必须在基准范围之外删除它们。此外,我认为您必须多次重新运行测试并取平均值而不是单次运行。

【讨论】:

  • 我什至会从平均值中排除第一次运行的结果,如果要允许运行时有一个允许正确“唤醒”的宽限期。
  • Java 8 Math.random() 和自动装箱不太可能变得那么慢……
  • 我还要说 HashMap 不太可能受到性能影响,但我们到了。从中获得的要点是,当您进行性能测试时,您需要正确地进行或根本不进行。这也意味着尽可能多地消除影响因素。
猜你喜欢
  • 1970-01-01
  • 2018-04-09
  • 2018-03-02
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多