Tom*_*rds 5 java loops for-loop java-stream jmh
我目前正在开展一个项目,使用 Java Microbenchmark Harness (JMH) 框架来测量 Java 中不同类型循环的速度。我得到了一些关于流的有趣结果,我无法解释这些结果,并且想知道更好地理解流和数组列表的人是否可以帮助我解释我的结果。
基本上,当迭代大小为 100 左右的数组列表时,stream.forEach 方法比任何其他类型的循环快得多:
我的结果图表如下所示: https ://i.stack.imgur.com/W34eA.png
我尝试过使用对象和字符串的数组列表,并且都产生类似的结果。随着列表的大小变大,流方法仍然更快,但其他列表之间的性能差距变得更小。我不知道是什么导致了这些结果。
@Fork(5)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class StackOverflowQ {
List<Integer> intList;
int size = 100;
@Setup
public void setup() {
intList = new ArrayList<>(size);
for(int i = 0; i<size;i++){
intList.add(i);
}
}
/**
Work done to each item in the loop.
*/
public double doWork(int item) {
return item + 47;
}
@Benchmark
public void standardFor(Blackhole bh){
for (int i = 0; i<intList.size(); i++){
bh.consume(doWork(intList.get(i)));
}
}
@Benchmark
public void streamForEach(Blackhole bh){
intList.stream().forEach(i -> bh.consume(doWork(i)));
}
}
Run Code Online (Sandbox Code Playgroud)
这个答案谈到了java.util.ArrayList
。其他Collection
实现可能(并且确实!)显示完全不同的结果。
简而言之:因为流使用Spliterator
而不是Iterator
Iterator
基于迭代基于hasNext
和next
方法调用,其中第二个方法调用会改变Iterator
实例。这会带来一些性能成本。Spliterator
支持批量迭代。在这里阅读更多相关内容。增强的 for 循环 ( for (T e : iterable) { ... }
) 似乎使用了Iterator
.
性能通常应该是次要的;您应该使用最能描述您意图的结构。虽然由于向后兼容性的原因,这会很困难,但也许基于 spliterator 的 forEach 和增强的 for 循环(on ArrayList
)之间的性能差异将来会消失。
索引 for 循环怎么样?( List#get(int)
)
\n它们表现出比分离器性能更差的部分原因是它们需要验证索引。其他原因可能包括方法调用,例如。获取索引处的数据,而Spliterator
直接访问数组。但这纯粹是猜测。
下面您可以看到一个微小的基准,它证实了上述理论。请注意,最佳情况下基准测试应该运行更长时间。
\n@State(Scope.Benchmark)\n@Fork(value = 2)\n@Warmup(iterations = 2, time = 3)\n@Measurement(iterations = 2, time = 3)\npublic class A {\n \n public List<Object> list;\n \n @Setup\n public void setup() {\n list = new ArrayList<>();\n for (int i = 0; i < 1000; i++) list.add(i);\n }\n \n @Benchmark\n public void collectionForEach(Blackhole hole) {\n list.forEach(hole::consume);\n }\n \n @Benchmark\n public void iteratorFor(Blackhole hole) {\n for (Iterator<Object> iterator = list.iterator(); iterator.hasNext(); ) {\n hole.consume(iterator.next());\n }\n }\n \n @Benchmark\n public void enhancedFor(Blackhole hole) {\n for (Object e : list) {\n hole.consume(e);\n }\n }\n \n @Benchmark\n public void iteratorForEach(Blackhole hole) {\n list.iterator().forEachRemaining(hole::consume);\n }\n \n @Benchmark\n public void indexedFor(Blackhole hole) {\n for (int i = 0, size = list.size(); i < size; i++) {\n hole.consume(list.get(i));\n }\n }\n \n @Benchmark\n public void streamForEach(Blackhole hole) {\n list.stream().forEach(hole::consume);\n }\n \n @Benchmark\n public void spliteratorForEach(Blackhole hole) {\n list.spliterator().forEachRemaining(hole::consume);\n }\n}\n
Run Code Online (Sandbox Code Playgroud)\n以及结果。“ops/s”表示每秒操作次数,越高越好。
\nBenchmark Mode Cnt Score Error Units\nA.collectionForEach thrpt 4 158047.064 \xc2\xb1 959.534 ops/s\nA.iteratorFor thrpt 4 177338.462 \xc2\xb1 3245.129 ops/s\nA.enhancedFor thrpt 4 177508.037 \xc2\xb1 1629.494 ops/s\nA.iteratorForEach thrpt 4 184919.605 \xc2\xb1 1922.114 ops/s\nA.indexedFor thrpt 4 193318.529 \xc2\xb1 2715.611 ops/s\nA.streamForEach thrpt 4 280963.272 \xc2\xb1 2253.621 ops/s\nA.spliteratorForEach thrpt 4 283264.539 \xc2\xb1 3055.967 ops/s\n
Run Code Online (Sandbox Code Playgroud)\n