JDK 11和JDK 13的性能对比

7

更新

在评论中发现我采用的基准测试方法是错误的,因此结果是误导性的。在更正我的方法后(如接受的答案所示),结果与预期相同——JDK 13的性能与JDK 11一样好。有关详细信息,请参见答案。

原始问题

我正在对 Windows 10 下的 HashSet 进行一些性能基准测试,使用以下 JMH 测试代码:

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, warmups = 1)
public void init() {
    HashSet<String> s = new HashSet<>();
    for (int i = 0; i < 1000000; i++) {
        s.add(Math.random() + "");
    }
    s.size();
}

我在不同的JDK版本下编译并运行了它,以下是我的测试结果:

enter image description here

我还尝试了不同的堆大小(因此每个JDK有3种不同的颜色)。 当然,JDK 14只是今天的预发布快照-只是为了看看ZGC在Windows下的表现。

我想知道,在JDK 11之后发生了什么?(请注意,对于JDK 12,即使它不在上图中,它也已经开始增长)


1
如果您使用Integer.toString(i)而不是Math.random() + "",结果是否相似?我很好奇回归是由于生成随机数还是HashSet本身。 - Jacob G.
3
等等……你的 init 方法没有返回值?而且你也没有 Blackhole::consume 方法?如果你把这个 warmups 也删除了会发生什么呢?在我看来,如果你只使用 C2 编译器运行这段代码,所有值都应该接近于零,因为这个方法可以被视为 NOOP(无操作)。 - Eugene
2
@Eugene 不行,因为 Math.random() 会推进全局可见的 Random 种子的状态。基本上,这段代码是在测试 Math.random() 的效率... - Holger
@Holger 但是如果你用“肉眼”看它,这就不是那么明显了。 - Eugene
5
JDK 11和13之间的G1 GC发生了很大的变化。不确定哪个版本有问题,但差异显然是由垃圾回收引起的 - 您可以通过添加-prof gc JMH选项或切换到Parallel GC来看到这一点,后者在两个JDK版本中表现几乎相同。请注意,您的基准测试测量HashSet的性能。根据async-profiler的数据,约50%的CPU时间用于GC,~25%用于将double转换为String。 - apangin
3
@Eugene 在基准测试中错误地使用随机数是一个常见的错误,虽然在这种特定情况下,它可以避免死代码消除。在某些Java 7版本中,单独创建 HashMap 就会更新全局随机数种子(那个可怕的 alt-hashing 特性)。 - Holger
1个回答

1

感谢您在评论中提出的所有建议。

答案很可能是 Math.random() 或者 HashSet,或者缺少 Blackhole::consume,或者所有这些的组合。我将测试更改为简单地执行 i + "aaaaaaaaa",并用适当大小的预初始化 ArrayList 替换了 HashSet,以容纳所有要填充的值。最后,我添加了 Blackhole::consume 以排除不需要的 JIT 优化。

经过所有这些操作,计时从JDK 8到11逐渐下降,然后在JDK 11-13之间保持稳定。在JDK 14中,它略有上升,但好吧 - 它还未发布。


我脑海中仍有一个开放性问题 - 即使基准代码不理想,为什么它会给出一致的结果(尽管运行多次),证明了Math.random()HashSet的性能下降?这让我有点担心。此外,图表上呈现的结果与Eclipse启动所需时间相一致(从闪屏到主窗口出现)。令人惊讶的是,使用JDK 12-13(14秒)启动Eclipse比使用JDK 9-11(10秒)要慢得多。这应该让人担心吗? - Googie
3
请编辑您的问题,以反映您已经意识到问题可能与外部因素(随机)或基准测试方法有关。否则,那些只看问题而不往下回答的人会得出错误的结论:“Java 14很慢!” - Brian Goetz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接