我有以下代码(仅是我为这个问题写的一个示例),它只是计算范围的总和
我以三种方式实现它:
序列号
并行流
与ForkJoinPool
令人惊讶的是,串行方法是最快的方法。实际上,这花费了其他两个时间的%10。
为了使其更快,Java Stream的正确配置是什么?
我在做什么错?
package ned.main;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.IntStream;
public class TestParallelStream {
static private void testParallelStream() {
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1000000");
ForkJoinPool forkJoinPool = new ForkJoinPool(10000);
Date start = new Date();
long sum1 = 0;
for (int i = 0; i < 1_000_000; ++i) {
sum1 += i * 10;
}
Date start1 = new Date();
long sum2 = IntStream.range(1, 1_000_000)
.parallel()
.map(x -> x * 10)
.sum();
Date start2 = new Date();
try {
long sum3 = forkJoinPool.submit(() ->
IntStream
.range(1, 1_000_000)
.parallel()
.map(x -> x * 10)
.sum())
.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
long serial = start1.getTime() - start.getTime();
long parallelstream = start2.getTime() - start1.getTime();
long withfork = start2.getTime() - start1.getTime();
System.out.println(serial + "," + parallelstream + "," + withfork);
}
public static void main(String[] args) {
testParallelStream();
}
}
谢谢
最佳答案
似乎对并行性有根本的错误理解。要利用所有CPU内核进行计算,并行性应与内核数匹配,这已经是默认设置。
将并行度设置为1000000是没有意义的,即使在极少数情况下您确实拥有1000000处理器,在这种情况下,设置已经为默认值仍然过时。附带说明一下,如果您有1000000个处理单元,那么包含1000000个乘法的作业将太小而无法从该硬件中受益。您为每个int
乘法启动一个线程,这很疯狂。
如有疑问,请不要混淆该设置,并将并行性保留为默认设置。
是否从并行处理中受益,仍然取决于实际操作。 JVM的优化器将仅处理顺序代码的小块,因此将操作拆分为要并行处理的块可能会降低代码优化的好处。
在最极端的变体中,形式为循环
long sum1 = 0;
for(int i=from; i<to; ++i) sum1 += i * constant;
可以优化为
long sum1=((long)from+to-1)*(to-from)/2 * constant;
这会导致任意范围的计算时间保持不变,因此将范围分成多个子范围(要并行计算)通常不会缩短所需的时间。但这当然是特定于JVM的。
如果HotSpot具有非常严格的内联阈值,则使用流代码执行操作可能会超过阈值,从而降低了JVM的优化潜力。是否发生这种情况,也可以通过对等效顺序流操作进行基准测试来进行测试。在最好的情况下,它的性能应与循环完全相同。如果不是这样,您将知道流操作相对于循环也将具有不利之处,后者同样适用于并行流。调整JVM选项可能会有所帮助(希望默认值将来会变得更加“流友好”)。