如何有效地并行计算pi的计算(仅作为示例)?
这可行(并且在我的机器上大约需要15秒):
Stream.iterate(1d, d->-(d+2*(Math.abs(d)/d))).limit(999999999L).mapToDouble(d->4.0d/d).sum()
但以下所有并行变体都会遇到OutOfMemoryError
DoubleStream.iterate(1d, d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999L).map(d->4.0d/d).sum();
DoubleStream.iterate(1d, d->-(d+2*(Math.abs(d)/d))).limit(999999999L).parallel().map(d->4.0d/d).sum();
DoubleStream.iterate(1d, d->-(d+2*(Math.abs(d)/d))).limit(999999999L).map(d->4.0d/d).parallel().sum();
那么,我需要做些什么才能并行处理这个(大)流? 我已经检查过autoboxing是否导致内存消耗,但事实并非如此。这也有效:
DoubleStream.iterate(1, d->-(d+Math.abs(2*d)/d)).boxed().limit(999999999L).mapToDouble(d->4/d).sum()
答案 0 :(得分:3)
问题在于您使用的是难以并行化的构造。
首先,Stream.iterate(…)
创建一个数字序列,其中每个计算都取决于先前的值,因此,它没有提供并行计算的空间。更糟糕的是,它创建了一个无限的流,它将由实现处理,就像一个未知大小的流。对于拆分流,必须先将值收集到数组中,然后才能将它们移交给其他计算线程。
其次,提供limit(…)
不会改善情况,it makes the situation even worse。应用限制会删除实现刚为阵列片段收集的大小信息。原因是流是有序,因此处理数组片段的线程不知道它是否可以处理所有元素,因为这取决于其他线程正在处理的先前元素的数量。这是documented:
“...在有序的并行管道上可能相当昂贵,特别是对于
maxSize
的大值,因为limit(n)
被限制为不仅返回任何 n 元素,而是遇到订单中的前n个元素。“
我们完全知道iterate
返回的无限序列与limit(…)
的组合实际上具有完全已知的大小,这真是遗憾。但实施不知道。并且API没有提供创建两者的有效组合的方法。但我们可以自己做:
static DoubleStream iterate(double seed, DoubleUnaryOperator f, long limit) {
return StreamSupport.doubleStream(new Spliterators.AbstractDoubleSpliterator(limit,
Spliterator.ORDERED|Spliterator.SIZED|Spliterator.IMMUTABLE|Spliterator.NONNULL) {
long remaining=limit;
double value=seed;
public boolean tryAdvance(DoubleConsumer action) {
if(remaining==0) return false;
double d=value;
if(--remaining>0) value=f.applyAsDouble(d);
action.accept(d);
return true;
}
}, false);
}
一旦我们有了这样的迭代限制方法,我们可以像
一样使用它iterate(1d, d -> -(d+2*(Math.abs(d)/d)), 999999999L).parallel().map(d->4.0d/d).sum()
由于源的顺序性,这仍然不会从并行执行中获益,但它可以工作。在我的四核机器上,它获得了大约20%的收益。
答案 1 :(得分:0)
这是因为ForkJoinPool
方法使用的默认parallel()
实现不会限制创建的线程数。解决方案是提供ForkJoinPool
的自定义实现,该实现仅限于并行执行的线程数。这可以通过以下方式实现:
ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
forkJoinPool.submit(() -> DoubleStream.iterate(1d, d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999L).map(d->4.0d/d).sum());