如何解释Java 8和Java 7中HashMap的Insert性能



我对Java 8 HashMap进行了改进,显示使用二进制树可以将搜索性能提高约20%。

在这一点上,有一个想法是如何影响插入性能的。所以我开始插入几百万条记录。下面是代码片段和不同的结果集。

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

结果表明,多达100万次的插入Java 8的性能更好。但当你向上移动时,结果显示出相反的行为。

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

更新:感谢各位宝贵的反馈。我需要学习更多关于基准测试的知识。我预先初始化了Math.random部分,结果对于java7和java8都是相同的。这是修改后的代码。请告诉我,从基准测试的角度来看,代码是否仍然很臭。

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());
}
}

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

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

  • 一般来说,在测试运行期间运行探查器是一个好主意,以验证在您想要对进行基准测试的代码中确实花费了时间

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

请注意,Java 8 HashMap需要稍多的内存,当仅测量初始化成本时,即使稍多的存储器需求也可能会产生很大影响。

在我的机器上,给两个JVM至少1GB的初始内存进行1000万次插入,导致在Java 8上花费大约5秒,在Java 7上花费大约6秒,即使没有预热。远离你的20秒更不要说超过一分钟。

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

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

最新更新