在Java 8中比较传统的循环,流和parallelStream [英] Comparison between legacy for loop, streams and parallelStream in Java 8

查看:157
本文介绍了在Java 8中比较传统的循环,流和parallelStream的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  import java.util.ArrayList; 
import java.util.List;

public class IterationBenchmark {

public static void main(String args []){
List< String> persons = new ArrayList< String>();
persons.add(AAA);
persons.add(BBB);
persons.add(CCC);
persons.add(DDD);
long timeMillis = System.currentTimeMillis();
for(String person:persons)
System.out.println(person);
System.out.println(for循环的遗留时间:+
(System.currentTimeMillis() - timeMillis));
timeMillis = System.currentTimeMillis();
persons.stream()。forEach(System.out :: println);
System.out.println(序列流花费的时间:+
(System.currentTimeMillis() - timeMillis));
timeMillis = System.currentTimeMillis();
persons.parallelStream()。forEach(System.out :: println);
System.out.println(并行流所花费的时间:+
(System.currentTimeMillis() - timeMillis));




$ b $ p $输出:

  AAA 
BBB
CCC
DDD
Legacy for循环花费的时间:0

AAA
BBB
CCC
DDD
顺序流所需时间:49

CCC
DDD
AAA
BBB
并行流所花费的时间:3

为什么Java 8 Stream API的性能是非常低的,比传统的for循环?

解决方案

慢,因为你需要加载许多辅助类,为lambdas生成许多匿名类和JIT编译许多方法。因此,通常第一个Stream操作需要几十毫秒。连续的呼叫速度要快得多,可能会超过1 us,具体取决于确切的流操作。如果您交换并行流测试和顺序流测试,顺序流将会快得多。所有的辛苦工作都是由第一个人完成的。



我们编写一个JMH基准测试,以正确地预热您的代码并独立测试所有的情况:

  import java.util.concurrent.TimeUnit; 
import java.util。*;
import java.util.stream。*;

import org.openjdk.jmh.annotations。*;

@Warmup(iterations = 5,time = 1000,timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10,time = 1000,timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
@State(Scope.Benchmark)
public class StreamTest {
列表与LT;字符串>者;
@Setup
public void setup(){
persons = new ArrayList< String>();
persons.add(AAA);
persons.add(BBB);
persons.add(CCC);
persons.add(DDD);


@Benchmark
public void loop(){
for(String person:persons)
System.err.println(person);


@Benchmark
public void stream(){
persons.stream()。forEach(System.err :: println);


@Benchmark
public void parallelStream(){
persons.parallelStream()。forEach(System.err :: println);




$ b $ p
$ b $ p $这里我们有三个测试: loop stream parallelStream 。请注意,我将 System.out 更改为 System.err 。这是因为通常使用 System.out 来输出JMH结果。我将 System.err 的输出重定向到 nul ,所以结果应该取决于我的文件系统或控制台子系统(这在Windows上是特别慢的)。所以结果是(酷睿i7-4702MQ CPU @ 2.2GHz,4核HT,Win7,Oracle JDK 1.8.0_40) :

$ $ $ $ $ $ $ $基准模式Cnt得分错误单位
StreamTest.loop avgt 30 42.410±1.833 us / op
StreamTest.parallelStream avgt 30 76.440±2.073 us / op
StreamTest.stream avgt 30 42.820±1.389 us / op

我们看到 stream loop 产生完全相同的结果。差异在统计上不显着。实际上Stream API比循环慢一些,但是最慢的部分是 PrintStream 。即使输出到 nul ,与其他操作相比,IO子系统也非常慢。所以我们只测量了Stream API或者循环速度,但是 println 速度。

另外看看,这是微秒,因此流版本实际上工作的速度比您的测试快1000倍。



为什么 parallelStream 要慢得多?仅仅因为你不能并行写入同一个 PrintStream ,因为它在内部是同步的。所以 parallelStream 完成了将4元素列表分解为4个子任务的艰辛工作,在不同的线程中调度作业,同步它们,但是这绝对是徒劳的因为最慢的操作( println )不能并行执行:当其中一个线程正在工作时,其他人正在等待。一般来说,并行化在同一互斥体上同步的代码是无用的(这是你的情况)。


import java.util.ArrayList;
import java.util.List;

public class IterationBenchmark {

    public static void main(String args[]){
        List<String> persons = new ArrayList<String>();
        persons.add("AAA");
        persons.add("BBB");
        persons.add("CCC");
        persons.add("DDD");
        long timeMillis = System.currentTimeMillis();
        for(String person : persons)
            System.out.println(person);
        System.out.println("Time taken for legacy for loop : "+
                  (System.currentTimeMillis() - timeMillis));
        timeMillis = System.currentTimeMillis();
        persons.stream().forEach(System.out::println);
        System.out.println("Time taken for sequence stream : "+
                  (System.currentTimeMillis() - timeMillis));
        timeMillis = System.currentTimeMillis();
        persons.parallelStream().forEach(System.out::println);
        System.out.println("Time taken for parallel stream : "+
                  (System.currentTimeMillis() - timeMillis));

    }
}

Output:

AAA
BBB
CCC
DDD
Time taken for legacy for loop : 0

AAA
BBB
CCC
DDD
Time taken for sequence stream : 49

CCC
DDD
AAA
BBB
Time taken for parallel stream : 3

Why the Java 8 Stream API performance is very low compare to legacy for loop?

解决方案

Very first call to the Stream API in your program is always quite slow, because you need to load many auxiliary classes, generate many anonymous classes for lambdas and JIT-compile many methods. Thus usually very first Stream operation takes several dozens of milliseconds. The consecutive calls are much faster and may fall beyond 1 us depending on the exact stream operation. If you exchange the parallel-stream test and sequential stream test, the sequential stream will be much faster. All the hard work is done by one who comes the first.

Let's write a JMH benchmark to properly warm-up your code and test all the cases independently:

import java.util.concurrent.TimeUnit;
import java.util.*;
import java.util.stream.*;

import org.openjdk.jmh.annotations.*;

@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
@State(Scope.Benchmark)
public class StreamTest {
  List<String> persons;
  @Setup
  public void setup() {
    persons = new ArrayList<String>();
    persons.add("AAA");
    persons.add("BBB");
    persons.add("CCC");
    persons.add("DDD");
  }

  @Benchmark
  public void loop() {
    for(String person : persons)
      System.err.println(person);
  }

  @Benchmark
  public void stream() {
    persons.stream().forEach(System.err::println);
  }

  @Benchmark
  public void parallelStream() {
    persons.parallelStream().forEach(System.err::println);
  }
}

Here we have three tests: loop, stream and parallelStream. Note that I changed the System.out to System.err. That's because System.out is used normally to output the JMH results. I will redirect the output of System.err to nul, so the result should less depend on my filesystem or console subsystem (which is especially slow on Windows).

So the results are (Core i7-4702MQ CPU @ 2.2GHz, 4 cores HT, Win7, Oracle JDK 1.8.0_40):

Benchmark                  Mode  Cnt   Score   Error  Units
StreamTest.loop            avgt   30  42.410 ± 1.833  us/op
StreamTest.parallelStream  avgt   30  76.440 ± 2.073  us/op
StreamTest.stream          avgt   30  42.820 ± 1.389  us/op

What we see is that stream and loop produce exactly the same result. The difference is statistically insignificant. Actually Stream API is somewhat slower than loop, but here the slowest part is the PrintStream. Even with output to nul the IO subsystem is very slow compared to other operations. So we just measured not the Stream API or loop speed, but println speed.

Also see, it's microseconds, thus stream version actually works 1000 times faster than in your test.

Why parallelStream is much slower? Just because you cannot parallelize the writes to the same PrintStream, because it is internally synchronized. So the parallelStream did all the hard work to splitting 4-element list to the 4 sub-tasks, schedule the jobs in the different threads, synchronize them properly, but it's absolutely futile as the slowest operation (println) cannot perform in parallel: while one of threads is working, others are waiting. In general it's useless to parallelize the code which synchronizes on the same mutex (which is your case).

这篇关于在Java 8中比较传统的循环,流和parallelStream的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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