Java 8,列表的第一次处理比后续处理慢 [英] Java 8, first processing of a list is slower than the subsequent processing

查看:135
本文介绍了Java 8,列表的第一次处理比后续处理慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm runnning some tests (very basic, nothing fancy) in order to check performance on Java 8 streams and lambdas. Using an ArrayList of 10 million POJOS, all I want to do is get the average value of a BigDecimal field. In order to take more than one sample, I run the process five times, and to my surprise the first of those five runs is extremely slower than the rest. I'm getting values like 0.38 seconds the first time, and 0.04 seconds on the other four. This is 10x faster!!! I also did the same test using old school for(Pojo p : pojos) with similar results. Why is this happening, and how can I take advantage of it? The code I'm using is:

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


推荐答案

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

There's a constant delay necessary to initialize the Stream API when you call it for the first time, which includes the following steps:


  • 从<$ c加载许多帮助程序类$ c> java.util.stream package

  • java.lang.invoke加载lambda生成类包(如 LambdaMetafactory )。

  • 生成lambdas的运行时表示和流管道中涉及的方法引用(包括流内部使用的lambda) API)。

  • 所有这些字节代码的分层编译(Interpreter - > C1 JIT - > C2 JIT)。 C2 JIT编译(生成最快的代码)仅在特定数量的方法调用(如5000)之后或在特定数量的后备之后触发(如果方法内部有大循环,则循环迭代;如40000)。当大多数代码不是C2编译时,它的工作速度要慢得多。此外,JIT编译器线程占用一些CPU时间,可用于实际计算。

  • 对于并行流:初始化常见的 ForkJoinPool ,创建新线程。

  • Loading of many helper classes from java.util.stream package
  • Loading lambda generating classes from java.lang.invoke package (like LambdaMetafactory).
  • Generating runtime representation for lambdas and method references involved into stream pipeline (including lambdas used internally in the Stream API).
  • Tiered compilation of all this byte code (Interpreter -> C1 JIT -> C2 JIT). C2 JIT compilation (which generates the fastest code) is triggered only after specific number of method invocation (like 5000) or after specific number of backedges (loop iterations if the method has big loop inside; like 40000). When most of code is not C2-compiled, it works much slower. Also JIT-compiler thread takes some CPU time which could be spent for actual computation.
  • For parallel streams: initialization of common ForkJoinPool, creating new threads.

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

All of these steps are performed only once. When you use Stream API again, most of this work is already done, so the consecutive launches are much faster.

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

In your particular case you are using the heap intensively, so heap enlargement could also be the cause of additional slowness. If your -Xms default value is too small, then Garbage collector performs several full-gc cycles until it enlarges heap to the comfortable size. You may run your test with Xms==Xmx (e.g. -Xmx1G -Xms1G) and this may improve the first iteration speed.

这篇关于Java 8,列表的第一次处理比后续处理慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆