2016-03-02 61 views
1

为了检查Java 8流和lambdas上的性能,我正在运行一些测试(非常基本,没什么奇怪的)。使用1000万POJOS中的一个ArrayList,我所要做的就是获得BigDecimal字段的平均值。为了取得多于一个样本,我运行了这个过程五次,令我吃惊的是这五次运行中的第一次比其他运行慢得多。我第一次获得的值是0.38秒,其他四个则是0.04秒。这比快10倍!我也使用旧学校for(Pojo p : pojos)做了相同的测试,结果类似。为什么会发生这种情况,我该如何利用它?我正在使用的代码是:Java 8,首次处理列表比后续处理慢

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."); 
} 
+3

不幸的是,进行性能测试并不容易,尤其是在与lambda表达式和方法引用结合使用时。你需要使用适当的工具,比如JMH框架。 – Tunaki

+1

我不同意重复标记。 OP询问为何第一次处理速度较慢。事实确实如此。即使OP使用JMH重写了基准,第一次迭代将比后续迭代慢得多。考虑到我们讲几十毫秒,而不是微秒或纳秒,OPs测量在方法上并不是那么糟糕。 –

回答

4

有必要的,当你把它的第一次,包括以下步骤初始化流API恒定的延迟:

  • 加载很多帮手java.util.stream
  • java.lang.invoke包(如LambdaMetafactory)加载lambda生成类。
  • 为涉及流管道(包括Stream API中内部使用的lambdas)生成lambda表达式和方法引用的运行时表示。
  • 所有这些字节码的分层编译(解释器 - > C1 JIT - > C2 JIT)。只有在特定数量的方法调用(如5000)或特定数量的支持(循环迭代,如果该方法内部有大循环;如40000)后才会触发C2 JIT编译(生成最快的代码)。当大多数代码不是C2编译时,它的运行速度要慢得多。另外,JIT编译器线程需要一些CPU时间,可用于实际计算。
  • 对于并行流:初始化公共ForkJoinPool,创建新线程。

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

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

+1

也许值得向“我该如何利用它?”的一般答案增加一些问题:通常,避免代码重复,创建可重用类等。 – Holger

+0

谢谢你们两位! – Emilio