Java 8,列表的第一次处理比后续处理慢

时间:2016-03-02 21:53:27

标签: performance java-8 java-stream

我正在运行一些测试(非常基本,没什么特别的),以便检查Java 8流和lambda的性能。使用1000万POJOS的ArrayList,我想要做的就是获得BigDecimal字段的平均值。为了获取多个样本,我运行了五次,令我惊讶的是,这五次运行中的第一次比其他运行速度慢得多。我第一次获得0.38秒的值,而其他四个值获得0.04秒。这快10倍!!!我也使用旧学校for(Pojo p : pojos)做了同样的测试,结果相似。为什么会发生这种情况,我该如何利用它呢?我使用的代码是:

for (int i = 0; i < 5; i++) {
    long init = System.nanoTime();
    BigDecimal sum = lista.parallelStream().map(x -> x.getCosto()).reduce(BigDecimal.ZERO, BigDecimal::add);
    BigDecimal avg = sum.divide(BigDecimal.valueOf(registros));
    long end = System.nanoTime();
    System.out.println("End of processing: " + avg + " in "
            + ((end - init) / 1000000000.0) + " seconds.");
}

1 个答案:

答案 0 :(得分:4)

首次调用Stream API时,需要经常延迟初始化Stream API,其中包括以下步骤:

  • java.util.stream package
  • 加载许多帮助程序类
  • java.lang.invoke包中加载lambda生成类(如LambdaMetafactory)。
  • 生成lambdas的运行时表示和流管道中涉及的方法引用(包括Stream API内部使用的lambda)。
  • 所有这些字节代码的分层编译(Interpreter - &gt; C1 JIT - &gt; C2 JIT)。 C2 JIT编译(生成最快的代码)仅在特定数量的方法调用(如5000)之后或在特定数量的后备之后触发(如果方法内部有大循环,则循环迭代;如40000)。当大多数代码不是C2编译时,它的工作速度要慢得多。此外,JIT编译器线程需要一些CPU时间,可用于实际计算。
  • 对于并行流:初始化ForkJoinPool,创建新线程。

所有这些步骤仅执行一次。当您再次使用Stream API时,大部分工作已经完成,因此连续启动的速度要快得多。

在您的特定情况下,您正在密集使用堆,因此堆扩大也可能是导致额外缓慢的原因。如果您的-Xms默认值太小,那么垃圾收集器会执行几个完整的gc循环,直到它将堆扩大到舒适的大小。您可以使用Xms == Xmx(例如-Xmx1G -Xms1G)运行测试,这可以提高第一次迭代速度。