【问题标题】:Sequential stream is faster than parallel stream if number of iterations is increased如果增加迭代次数,顺序流比并行流快
【发布时间】:2019-09-28 02:42:47
【问题描述】:

我用最后的示例代码来衡量性能。

如果我调用 checkPerformanceResult 方法并将参数 numberOfTimes 设置为 100,则并行流的性能优于重要的顺序流(sequential=346,parallel=78)。

如果我将参数设置为 1000,则顺序流的性能优于并行流显着(sequential=3239,parallel=9337)。

我跑了很多次,结果都是一样的。

有人可以解释一下这种行为以及这里发生了什么吗?

public class ParallelStreamExample {
    public static long checkPerformanceResult(Supplier<Integer> s, int numberOfTimes) {
        long startTime = System.currentTimeMillis();
        for(int i = 0; i < numberOfTimes; i++) {

           s.get();
        }
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    public static int sumSequentialStreamThread() {
        IntStream.rangeClosed(1, 10000000).sum();
        return 0;
    }

    public static int sumParallelStreamThread() {
        IntStream.rangeClosed(1, 10000000)
                .parallel().sum();
        return 0;
    }

    public static void main(String[] args) {
        System.out.println(checkPerformanceResult(ParallelStreamExample::sumSequentialStreamThread, 1000));
        System.out.println("break");
        System.out.println(checkPerformanceResult(ParallelStreamExample::sumParallelStreamThread, 1000));
    }
}

【问题讨论】:

  • 微基准测试是出了名的不可靠。您运行每个测试一次。此外,您返回一个常数零。正确的算法是 return ((n + 1) * n) / 2; - 不需要循环或范围。
  • 在这种情况下返回一个常量应该没有任何影响?此外,我同意,这令人困惑。此外,我对解决求和问题的最快算法不感兴趣。我想看看流的表现。
  • 我认为如果有一种方法可以提示流减少操作可以通过分而治之的方法来完成,那么并行会更快。我认为它目前正在做的是产生N 线程,每个线程都有一个数字,然后终端操作仍然必须按顺序评估每个线程的值并组合相邻的线程。基本上,您正在创建大量工作人员,但强制他们全部按顺序通过您的sum 操作。换句话说,并行操作应该发生在 reducer (sum),而不是 producer (stream)
  • 我怀疑这(部分)是由于糟糕的基准设计/实施。使用 jmh (baeldung.com/java-microbenchmark-harness) 或类似方法重新进行基准测试。分析有问题的基准测试的结果没有什么意义。
  • @smac89 IntStream.rangeClosed(1, 10000000) .parallel() .sum();的并行处理没有问题这里没有“生产者-消费者”的事情。

标签: java java-8 parallel-processing java-stream


【解决方案1】:

使用线程并不总是能让代码运行得更快

当使用几个线程时,管理每个线程总是会产生开销(将 CPU 时间分配给操作系统给每个线程,管理在上下文切换时需要运行的下一行代码等......)

在这种特殊情况下

sumParallelStreamThread 中创建的每个线程在内存操作中都非常简单(调用返回数字的函数)。

所以sumSequentialStreamThreadsumParallelStreamThread 之间的区别在于sumParallelStreamThread 中的每个简单操作都有创建线程并运行它的开销(假设后台没有发生任何线程优化)。

sumSequentialStreamThread 做同样的事情而无需管理所有线程的开销,这就是它运行得更快的原因。

何时使用线程

使用线程最常见的用例是当您需要执行大量 I/O 任务时。

什么是 I/O 任务?

这取决于几个因素,你可以找到关于它的辩论here。 但我认为通常人们会同意向某处发出 HTTP 请求或执行数据库查询可以被视为 I/O 操作。

为什么更适合?

因为 I/O 操作通常需要一段时间来等待与它们相关的响应。 例如,当查询数据库时,执行查询的线程将等待数据库返回响应(即使它不到半秒),而该线程正在等待不同的线程可以执行其他操作,这就是我们可以获得性能的地方.

我发现通常在不同线程中运行仅涉及 RAM 内存和 CPU 操作的任务会使代码运行速度比使用一个线程慢。

基准讨论

关于在 cmets 中看到的基准注释,我不确定它们是否正确,但在这些类型的情况下,我会根据任何分析工具(或仅使用它开始)仔细检查我的基准,例如JProfilerYoutKit 它们通常非常准确。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-20
    • 2019-10-11
    • 1970-01-01
    • 2017-03-15
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    相关资源
    最近更新 更多