Java 8中的legacy for循环,流和parallelStream之间的比较

时间:2015-09-01 10:01:45

标签: java performance for-loop java-8 java-stream

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));

    }
}

输出:

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

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

1 个答案:

答案 0 :(得分:11)

在程序中第一次调用Stream API总是很慢,因为你需要加载许多辅助类,为lambdas生成许多匿名类,并编译许多方法。因此,通常第一次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 {
  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);
  }
}

这里我们有三个测试:loopstreamparallelStream。请注意,我将System.out更改为System.err。这是因为System.out通常用于输出JMH结果。我将System.err的输出重定向到nul,因此结果应该更少地依赖于我的文件系统或控制台子系统(在Windows上尤其慢)。

结果是(Core i7-4702MQ CPU @ 2.2GHz,4核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

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

另请参阅,这是微秒,因此流版本实际上比您的测试快1000倍。

为什么parallelStream要慢得多?仅仅因为您无法将写入并行化到同一PrintStream,因为它是内部同步的。因此parallelStream完成了将4个元素列表拆分为4个子任务所做的所有艰苦工作,在不同的线程中安排作业,正确地同步它们,但它作为最慢的操作绝对是徒劳的( println)不能并行执行:当一个线程正在工作时,其他线程正在等待。通常,并行化在同一个互斥锁上同步的代码是没用的(这是你的情况)。