使用大型并行Java8流时如何防止堆空间错误

使用大型并行Java8流时如何防止堆空间错误,java,parallel-processing,java-8,java-stream,Java,Parallel Processing,Java 8,Java Stream,如何有效地并行计算pi(仅作为示例) 这可以工作(在我的机器上大约需要15秒): 但以下所有并行变量都会遇到OutOfMemory错误 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).

如何有效地并行计算pi(仅作为示例)

这可以工作(在我的机器上大约需要15秒):

但以下所有并行变量都会遇到OutOfMemory错误

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();
那么,我需要做什么来获得这个(大)流的并行处理呢? 我已经检查过自动装箱是否导致内存消耗,但不是。这也适用于:

DoubleStream.iterate(1, d->-(d+Math.abs(2*d)/d)).boxed().limit(999999999L).mapToDouble(d->4/d).sum()

这是因为
parallel()
方法使用的默认
ForkJoinPool
实现不限制创建的线程数。解决方案是提供
ForkJoinPool
的自定义实现,该实现仅限于并行执行的线程数。这可以通过以下方式实现:

ForkJoinPool-ForkJoinPool=newforkjoinpool(Runtime.getRuntime().availableProcessors());

submit(()->DoubleStream.iterate(1d,d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999 l).map(d->4.0d/d.sum())
这是因为
parallel()
方法使用的默认
ForkJoinPool
实现没有限制创建的线程数。解决方案是提供
ForkJoinPool
的自定义实现,该实现仅限于并行执行的线程数。这可以通过以下方式实现:

ForkJoinPool-ForkJoinPool=newforkjoinpool(Runtime.getRuntime().availableProcessors());

submit(()->DoubleStream.iterate(1d,d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999 l).map(d->4.0d/d.sum())
这是因为
parallel()
方法使用的默认
ForkJoinPool
实现没有限制创建的线程数。解决方案是提供
ForkJoinPool
的自定义实现,该实现仅限于并行执行的线程数。这可以通过以下方式实现:

ForkJoinPool-ForkJoinPool=newforkjoinpool(Runtime.getRuntime().availableProcessors());

submit(()->DoubleStream.iterate(1d,d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999 l).map(d->4.0d/d.sum())
这是因为
parallel()
方法使用的默认
ForkJoinPool
实现没有限制创建的线程数。解决方案是提供
ForkJoinPool
的自定义实现,该实现仅限于并行执行的线程数。这可以通过以下方式实现:

ForkJoinPool-ForkJoinPool=newforkjoinpool(Runtime.getRuntime().availableProcessors());

submit(()->DoubleStream.iterate(1d,d->-(d+2*(Math.abs(d)/d))).parallel().limit(999999999 l).map(d->4.0d/d.sum())问题在于您使用的构造很难并行化

首先,
Stream.iterate(…)
创建一个数字序列,其中每个计算取决于前面的值,因此,它没有提供并行计算的空间。更糟糕的是,它创建了一个无限流,将由实现像大小未知的流一样处理。为了分割流,必须先将值收集到数组中,然后才能将其移交给其他计算线程

其次,提供
限制(…)
并不能改善这种情况。应用限制将删除实现刚刚为数组片段收集的大小信息。原因是流是有序的,因此处理数组片段的线程不知道是否可以处理所有元素,因为这取决于其他线程正在处理的先前元素的数量。这是:

“…在有序并行管道上,它可能非常昂贵,尤其是对于大值
maxSize
,因为
limit(n)
被约束为不仅返回任意n个元素,而且返回相遇顺序中的前n个元素。”

这是一个遗憾,因为我们完全知道,
iterate
返回的无限序列与
极限(…)
的组合实际上有一个确切的已知大小。但是实现并不知道。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%的增益。

问题是您使用的结构很难并行化

首先,
Stream.iterate(…)
创建一个数字序列,其中每个计算取决于前面的值,因此,它没有提供并行计算的空间。更糟糕的是,它创建了一个无限流,将由实现像大小未知的流一样处理。为了分割流,必须先将值收集到数组中,然后才能将其移交给其他计算线程

其次,提供
限制(…)
并不能改善这种情况。应用限制将删除实现刚刚为数组片段收集的大小信息。原因是流是有序的,因此处理数组片段的线程不知道是否可以处理所有元素,因为这取决于其他线程正在处理的先前元素的数量。这是:

“…在有序并行管道上,它可能非常昂贵,尤其是对于大值
maxSize
,因为
limit(n)
被约束为不仅返回任意n个元素,而且返回相遇顺序中的前n个元素。”

这很遗憾,因为我们完全知道,iterate(1d, d -> -(d+2*(Math.abs(d)/d)), 999999999L).parallel().map(d->4.0d/d).sum()