为什么在某些情况下 Java Stream.forEach 方法比其他循环更快?

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)

Tri*_*ary 8

这个答案谈到了java.util.ArrayList。其他Collection实现可能(并且确实!)显示完全不同的结果。

\n

简而言之:因为流使用Spliterator而不是Iterator

\n

解释:

\n

Iterator基于迭代基于hasNextnext方法调用,其中第二个方法调用会改变Iterator实例。这会带来一些性能成本。Spliterator支持批量迭代。在这里阅读更多相关内容。增强的 for 循环 ( for (T e : iterable) { ... }) 似乎使用了Iterator.

\n

性能通常应该是次要的;您应该使用最能描述您意图的结构。虽然由于向后兼容性的原因,这会很困难,但也许基于 spliterator 的 forEach 和增强的 for 循环(on ArrayList)之间的性能差异将来会消失。

\n

索引 for 循环怎么样?( List#get(int))
\n它们表现出比分离器性能更差的部分原因是它们需要验证索引。其他原因可能包括方法调用,例如。获取索引处的数据,而Spliterator直接访问数组。但这纯粹是猜测。

\n

微小的 JMH 基准

\n

下面您可以看到一个微小的基准,它证实了上述理论。请注意,最佳情况下基准测试应该运行更长时间。

\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”表示每秒操作次数,越高越好。

\n
Benchmark              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