为什么在某些情况下,Java Stream.forEach方法比其他循环更快?
我目前正在进行一个项目,在该项目中,我使用Java微基准线束(JMH)框架测量Java中不同类型循环的速度。我得到了一些关于流的有趣结果,但我无法解释,我想知道是否有更了解流和数组列表的人可以帮助我解释我的结果 基本上,当遍历大小约为100的数组列表时,stream.forEach方法比任何其他类型的循环快得多: 我的结果图表如下所示: 我尝试过使用对象和字符串的数组列表,结果都很相似。随着列表的大小越来越大,stream方法的速度会更快,但其他列表之间的性能差距会越来越小。我不知道是什么导致了这些结果为什么在某些情况下,Java Stream.forEach方法比其他循环更快?,java,loops,for-loop,java-stream,jmh,Java,Loops,For Loop,Java Stream,Jmh,我目前正在进行一个项目,在该项目中,我使用Java微基准线束(JMH)框架测量Java中不同类型循环的速度。我得到了一些关于流的有趣结果,但我无法解释,我想知道是否有更了解流和数组列表的人可以帮助我解释我的结果 基本上,当遍历大小约为100的数组列表时,stream.forEach方法比任何其他类型的循环快得多: 我的结果图表如下所示: 我尝试过使用对象和字符串的数组列表,结果都很相似。随着列表的大小越来越大,stream方法的速度会更快,但其他列表之间的性能差距会越来越小。我不知道是什么导致
@Fork(5)
@基准模式(模式平均时间)
@输出时间单位(时间单位纳秒)
@预热(迭代次数=10,时间=100,时间单位=timeUnit.ms)
@测量(迭代次数=10,时间=100,时间单位=时间单位。毫秒)
@状态(Scope.Thread)
公共类StackOverflowQ{
列表intList;
int size=100;
@设置
公共作废设置(){
intList=新阵列列表(大小);
对于(inti=0;i这个答案谈论的是java.util.ArrayList
。其他集合
实现可能(也确实如此!)显示完全不同的结果
简而言之:因为流使用拆分器
而不是迭代器
说明:
基于Iterator
的迭代基于hasNext
和next
方法调用,第二个方法调用变异了Iterator
实例。这会带来一些性能成本。Spliterator
支持批量迭代。增强的for循环(for(te:iterable){…}
)似乎使用了迭代器
性能通常应该是次要考虑因素;您应该使用最能描述您的意图的结构。虽然由于向后兼容的原因,这将很困难,但基于拆分器的forEach和增强的for循环(在ArrayList
上)之间的性能差异可能在将来消失
为循环建立索引怎么样?(List#get(int)
)
它们的性能比拆分器差,部分原因是它们需要验证索引。其他原因可能包括方法调用,例如在索引处获取数据,而spliterator
直接访问数组。但这纯粹是推测
微型JMH基准
下面你可以看到一个微小的基准,它证实了上述理论。请注意,最好的基准应该运行更长的时间
@State(Scope.Benchmark)
@Fork(值=2)
@预热(迭代次数=2次,时间=3次)
@测量(迭代次数=2次,时间=3次)
公共A类{
公开名单;
@设置
公共作废设置(){
列表=新的ArrayList();
对于(inti=0;i<1000;i++)列表,添加(i);
}
@基准
公共void collectionForEach(黑洞){
list.forEach(洞::消耗);
}
@基准
公共无效迭代器(黑洞){
for(Iterator Iterator=list.Iterator();Iterator.hasNext();){
消费(迭代器.next());
}
}
@基准
公共空间增强(黑洞){
用于(对象e:列表){
洞。消耗(e);
}
}
@基准
公共无效迭代器foreach(黑洞){
list.iterator().forEachRemaining(hole::consume);
}
@基准
公共空隙指数(黑洞){
for(int i=0,size=list.size();i
结果是,“ops/s”表示每秒操作数,越高越好
Benchmark Mode Cnt Score Error Units
A.collectionForEach thrpt 4 158047.064 ± 959.534 ops/s
A.iteratorFor thrpt 4 177338.462 ± 3245.129 ops/s
A.enhancedFor thrpt 4 177508.037 ± 1629.494 ops/s
A.iteratorForEach thrpt 4 184919.605 ± 1922.114 ops/s
A.indexedFor thrpt 4 193318.529 ± 2715.611 ops/s
A.streamForEach thrpt 4 280963.272 ± 2253.621 ops/s
A.spliteratorForEach thrpt 4 283264.539 ± 3055.967 ops/s
您是否尝试从循环中提取size()
调用(使用size
而不是intList.size()
)-检查这个问题,它讨论了流的优点和缺点,这可能会有所帮助-您能确认流是sequential()吗
?为了正确起见,我将在您的基准测试中包含此显式调用。您还应该与For(Integer I:intList)bh.consume(doWork(I));
变量进行比较。此外,您可以在数组列表上直接使用forEach
,而不使用流。两个方法如何给出四个结果?