我有两个功能解决同样的问题。 第一个使用顺序流,第二个使用并行流。
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);
}
为什么并行流的循环速度较慢?
答案 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中解决。但是,您的操作不太可能从并行执行中受益。