当前位置: 首页 > 知识库问答 >
问题:

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

巫马劲
2023-03-14

我目前正在承担一个项目,我正在使用Java微基准线束(JMH)框架测量Java中不同类型循环的速度。我得到了一些关于流的有趣结果,我无法解释,并且想知道是否更了解流和数组列表的人可以帮助我解释我的结果。

基本上,当遍历大小为100的数组列表时,stream.forEach方法比任何其他类型的循环都快得多:

我的结果图表显示在这里:https://i.imgur.com/ypXoWWq.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)));
    }

}

共有1个答案

弓智明
2023-03-14

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

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

基于< code>Iterator的迭代基于< code>hasNext和< code>next方法调用,其中第二个调用会使< code>Iterator实例发生变异。这带来了一些性能成本。< code > split operator 支持批量迭代。点击此处了解更多信息。增强的for循环(< code>for (T e : iterable) {...})似乎使用了< code >迭代器。

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

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

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

@State(Scope.Benchmark)
@Fork(value = 2)
@Warmup(iterations = 2, time = 3)
@Measurement(iterations = 2, time = 3)
public class A {
    
    public List<Object> list;
    
    @Setup
    public void setup() {
        list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) list.add(i);
    }
    
    @Benchmark
    public void collectionForEach(Blackhole hole) {
        list.forEach(hole::consume);
    }
    
    @Benchmark
    public void iteratorFor(Blackhole hole) {
        for (Iterator<Object> iterator = list.iterator(); iterator.hasNext(); ) {
            hole.consume(iterator.next());
        }
    }
    
    @Benchmark
    public void enhancedFor(Blackhole hole) {
        for (Object e : list) {
            hole.consume(e);
        }
    }
    
    @Benchmark
    public void iteratorForEach(Blackhole hole) {
        list.iterator().forEachRemaining(hole::consume);
    }
    
    @Benchmark
    public void indexedFor(Blackhole hole) {
        for (int i = 0, size = list.size(); i < size; i++) {
            hole.consume(list.get(i));
        }
    }
    
    @Benchmark
    public void streamForEach(Blackhole hole) {
        list.stream().forEach(hole::consume);
    }
    
    @Benchmark
    public void spliteratorForEach(Blackhole hole) {
        list.spliterator().forEachRemaining(hole::consume);
    }
}

结果。“操作/秒”表示操作/秒,越高越好。

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
 类似资料:
  • 我遇到了一个非常奇怪的问题,java线程正忙着等待。 我有一个线程忙于等待其他线程的静态变量的状态。假设忙碌等待的线程正在等待另一个线程的静态int变量达到某个值 如果我使用上面的代码,线程将被卡在忙等待中,不会跳出while循环,即使确实达到5。 但是,如果我使用其他代码,那么线程确实会跳出忙等待循环。有时,一旦达到5,其他时候会晚一点。但它会发生。对于我的特定示例,我将其用作“无意义的工作”

  • 我用Kotlin和Jongo来访问MongoDB。Jongo使用Jackson来序列化/反序列化对象,以便从MongoDB中保存和读取它们。我使用Jackson-Kotlin模块来帮助使用构造函数序列化Kotlin数据类。 下面是一个序列化良好的数据类的示例: 下面是一个未能反序列化的类似类的示例: Jongo抛出以下异常,因为Jackson反序列化失败: 如果我像这样完整地注释会话数据类,它确实

  • 问题内容: 为什么 工作,但是 不是吗 问题答案: 为了理解这一点,让我们考虑一下编译器在两种可能性下每个步骤所做的事情。让我们开始: 编译器将‘4’转换为int。所以变成 然后编译器变成 ch是一个字符,编译器可以将54转换为字符,因为它可以证明转换没有损失。 现在让我们考虑第二个版本: ch在编译时没有已知值。因此,这成为 现在,编译器无法证明此(int)的结果在char范围内可存储。因此它

  • 首先,我知道这个问题在许多其他线程中都有描述。但是我无法找到并回答这个问题,为什么这个错误并不总是被抛出? 让我描述一下我的意思。我写了一些示例代码来说明这一点: DSA 最后进行比较测试: 在运行时,我们会收到这个描述的问题 Java . lang . illegalargumentexception:比较法违反了它的通用契约! 根据托收文件。排序方法: (可选)如果实现检测到列表元素的自然排序

  • 问题内容: 我正在尝试测试2个线程,一个线程具有较高的优先级,而另一个线程具有较低的优先级。 根据我的结果,有时低优先级线程速度更快,这怎么可能?我已经通过增加每个线程内的click变量来测试了不同优先级的线程。我也增加和减少了睡眠时间,但是什么也没有。 由于我是在没有后台运行繁重程序的情况下进行测试的,因此我决定在运行高清影片的情况下进行测试,但仍然没有真正的变化,线程的速度始终相同。 我的电脑

  • 问题内容: 我希望下面的代码在上引发编译时错误,因为未声明为throw ,但编译成功(在Java 1.7.0_45中),并且如果编译时错误为,则会生成您期望的输出固定。 如果更改为,它也会编译。 不会按预期方式编译: 这样编译: 这不是: 这也可以编译: 一个更复杂的示例-被检查的异常由外部catch块捕获,而不是被声明为抛出。这样编译: 因此,当编译器可以确定捕获的异常始终合法地重新抛出时,似乎