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性能非常低?
答案 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);
}
}
这里我们有三个测试:loop
,stream
和parallelStream
。请注意,我将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
我们看到stream
和loop
产生完全相同的结果。差异在统计上是微不足道的。实际上Stream API比循环慢一些,但这里最慢的部分是PrintStream
。即使输出到nul
,与其他操作相比,IO子系统也非常慢。所以我们测量的不是Stream API或循环速度,而是println
速度。
另请参阅,这是微秒,因此流版本实际上比您的测试快1000倍。
为什么parallelStream
要慢得多?仅仅因为您无法将写入并行化到同一PrintStream
,因为它是内部同步的。因此parallelStream
完成了将4个元素列表拆分为4个子任务所做的所有艰苦工作,在不同的线程中安排作业,正确地同步它们,但它作为最慢的操作绝对是徒劳的( println
)不能并行执行:当一个线程正在工作时,其他线程正在等待。通常,并行化在同一个互斥锁上同步的代码是没用的(这是你的情况)。