为什么并行流速较慢?

时间:2016-12-29 13:02:56

标签: java java-8 java-stream

我正在玩无限流,并将此程序用于基准测试。基本上,您提供的数字越大,完成的速度就越快。但是,我惊讶地发现,与顺序流相比,使用parellel流导致性能呈指数级下降。直觉上,人们会期望在多线程环境中生成和评估无限的随机数流,但事实并非如此。这是为什么?

    final int target = Integer.parseInt(args[0]);
    if (target <= 0) {
        System.err.println("Target must be between 1 and 2147483647");
        return;
    }

    final long startTime, endTime;
    startTime = System.currentTimeMillis();

    System.out.println(
        IntStream.generate(() -> new Double(Math.random()*2147483647).intValue())
        //.parallel()
        .filter(i -> i <= target)
        .findFirst()
        .getAsInt()
    );

    endTime = System.currentTimeMillis();
    System.out.println("Execution time: "+(endTime-startTime)+" ms");

3 个答案:

答案 0 :(得分:6)

我完全同意其他评论和答案,但实际上,如果目标非常低,您的测试会表现得很奇怪。在我的适度笔记本电脑上,当给出非常低的目标时,并行版本平均慢约60倍。这种极端的差异无法用流API中的并行化开销来解释,所以我也很惊讶:-)。 IMO的罪魁祸首在于:

Math.random()

在内部,此调用依赖于java.util.Random的全局实例。在documentation of Random中写道:

  

java.util.Random的实例是线程安全的。但是,并发   跨线程使用相同的java.util.Random实例可能会遇到   争用和随之而来的糟糕表现。考虑改为使用   多线程设计中的ThreadLocalRandom。

所以我认为与并行执行相比,并行执行的真正糟糕的性能是由随机的线程争用而不是任何其他开销来解释的。如果您使用ThreadLocalRandom(根据文档中的建议),那么性能差异将不会那么显着。另一种选择是实施更高级的数字供应商。

答案 1 :(得分:0)

第一次执行工作时,将工作传递给多个线程的成本很高。这个成本是相当固定的,所以即使你的任务很简单,开销也相对较高。

您遇到的一个问题是,效率极低的代码是确定解决方案执行情况的一种非常糟糕的方法。此外,它如何第一次运行以及它在几秒钟后如何运行通常可以100倍不同(可以更多)我建议使用一个已经最优的示例,然后尝试使用多个线程。

e.g。

long start = System.nanoTime();
int value = (int) (Math.random() * (target+1L));
long time = System.nanoTime() - value;
// don't time IO as it is sooo much slower
System.out.println(value);

注意:在代码已经预热并编译之前,这将不会有效。即忽略此代码运行的前2-5秒。

答案 2 :(得分:0)

根据各种答案的建议,我想我已经修好了。我不知道确切的瓶颈是什么,但在i5-4590T上,具有以下代码的并行版本比顺序版本执行得更快。为简洁起见,我仅包含(重构)代码的相关部分:

static IntStream getComputation() {
    return IntStream
            .generate(() -> ThreadLocalRandom.current().nextInt(2147483647));
}

static void computeSequential(int target) {
    for (int loop = 0; loop < target; loop++) {
        final int result = getComputation()
                    .filter(i -> i <= target)
                    .findAny()
                    .getAsInt();
        System.out.println(result);
    }
}

static void computeParallel(int target) {
     IntStream.range(0, target)
                .parallel()
                .forEach(loop -> {
                    final int result = getComputation()
                        .parallel()
                        .filter(i -> i <= target)
                        .findAny()
                        .getAsInt();
                    System.out.println(result);
                });
}

编辑:我还应该注意,我把它全部放在循环中以延长运行时间。