为什么并行流比顺序流慢?

时间:2015-02-12 10:54:55

标签: java java-8 java-stream

我有两个功能解决同样的问题。 第一个使用顺序流,第二个使用并行流。

public static int digitalRoot(int n) {
    int sum = String.valueOf(n).chars().map(i -> Integer.parseInt(String.valueOf((char) i))).sum();
    if (sum >= 10) {
        return digitalRoot(sum);
    } else {
        return sum;
    }
}

public static int digitalRootParallel(int n) {
    int sum = String.valueOf(n).chars().parallel().
            map(i -> Integer.parseInt(String.valueOf((char) i))).sum();
    if (sum >= 10) {
        return digitalRootParallel(sum);
    } else {
        return sum;
    }
}

我执行过一次这些功能。 并行函数 digitalRootParallel()比顺序(32 ms) digitalRoot()更快(5 ms)。

但是当我以1.000.000的循环执行每个循环时,顺序(117毫秒)比并行(1124毫秒)快。

    for (int i = 0; i < 1000000; i++) {
        sum = digitalRoot(n);
    }

为什么并行流的循环速度较慢?

2 个答案:

答案 0 :(得分:1)

所有我能分享的是我的惊喜,你曾测量过并行版本的减速。你给Fork / Join几乎没有工作要做:只需解析并总计十位数。 F / J可能会认为它甚至不值得分割它并将在单个线程上执行整个计算,但是这涉及的开销将扼杀性能。

如果您希望看到并行化的任何好处,那么请确保您至少有半秒的顺序计算,并且可以轻松拆分为子任务。

答案 1 :(得分:1)

首先,你的代码......有点复杂。而不是

String.valueOf(n).chars().map(i -> Integer.parseInt(String.valueOf((char) i))).sum();
你可以写

String.valueOf(n).chars().map(i -> i-'0').sum();

但是,即使使用原始代码,操作也非常简单,以至于HotSpot优化器可以将其转换为如此短的程度,以至于它比新线程的启动或同步更快地执行。

这尤其适用于您多次执行操作而未实际使用其结果的基准。在纯顺序上下文中,优化器可以识别结果未使用,并且如果没有任何副作用则忽略整个操作。相反,与其他线程通信的并行执行或更抽象的代码具有副作用。

因此,优化器不能对跨越多个线程的操作执行相同操作。 Stream实现也不能认识到操作的简单性。如果您请求并行执行,Stream实现将尊重它,假设您知道自己在做什么。


补充说明:

String.chars()当前实现的一个有趣属性是,它创建的PrimitiveIterator.OfInt将包含在Spliterator.OfInt中,而非实现{{} 1}}直接。这意味着没有直接的分裂能力,因为Spliterator.OfInt没有提供。相反,拆分将通过迭代一个或多个元素,将它们复制到一个数组中并提供此数组以供不同的线程处理来执行。

但是,迭代和复制到临时数组可能比顺序执行中的实际操作花费更多时间。

这些糟糕的拆分功能是known issue,将在Java 9中解决。但是,您的操作不太可能从并行执行中受益。