Java8并行流需要时间来求和值

时间:2018-07-05 04:34:13

标签: java parallel-processing java-8 out-of-memory java-stream

我正在练习java8并行流部分,并编写了一个程序,该程序将把作为参数传递的数字从0求和到该数字。

例如,如果我通过了10,它将对1到10的数字求和并返回输出。

下面是程序

public class ParellelStreamExample {



    public static void main(String[] args) {
        System.out.println("Long Range value - "+ Long.MIN_VALUE + " to "+ Long.MAX_VALUE);
        long startTime = System.nanoTime();
        long sum = sequentailSum(100000000);
        System.out.println(
                "Time in sequential execution " + (System.nanoTime() - startTime) / 1000000 + " msec with sum = " + sum);
        long startTime1 = System.nanoTime();
        long sum1 = parellelSum(100000000);
        System.out.println("Time in parallel execution " + (System.nanoTime() - startTime1) / 1000000
                + " msec with sum = " + sum1);

    }

    private static Long parellelSum(long n) {
        return Stream.iterate(1l, i -> i + 1).limit(n).parallel().reduce(0L, Long::sum);
    }

    private static Long sequentailSum(long n) {
        return Stream.iterate(1l, i -> i + 1).limit(n).reduce(0L, Long::sum);
    }
}

我收到的输出是

Long Range value - -9223372036854775808 to 9223372036854775807
Time in sequential execution 1741 msec with sum = 5000000050000000

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
    at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
    at java.util.stream.SliceOps$1.opEvaluateParallelLazy(SliceOps.java:155)
    at java.util.stream.AbstractPipeline.sourceSpliterator(AbstractPipeline.java:431)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.ReferencePipeline.reduce(ReferencePipeline.java:474)
    at com.abhishek.javainaction.stream.parellel.ParellelStreamExample.parellelSum(ParellelStreamExample.java:21)
    at com.abhishek.javainaction.stream.parellel.ParellelStreamExample.main(ParellelStreamExample.java:14)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.Long.valueOf(Long.java:840)
    at com.abhishek.javainaction.stream.parellel.ParellelStreamExample.lambda$0(ParellelStreamExample.java:21)
    at com.abhishek.javainaction.stream.parellel.ParellelStreamExample$$Lambda$3/250421012.apply(Unknown Source)
    at java.util.stream.Stream$1.next(Stream.java:1033)
    at java.util.Spliterators$IteratorSpliterator.trySplit(Spliterators.java:1784)
    at java.util.stream.AbstractShortCircuitTask.compute(AbstractShortCircuitTask.java:114)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

为什么该程序不能并行运行,并且发生gc开销, 相反,它应该在使用fork / join框架并通过线程进行内部处理的同时,在并行部分中运行得更快。

其中出了什么问题?

2 个答案:

答案 0 :(得分:6)

这里有几处出错了。

  1. 您正在尝试使用System.nanoTime()而不是JMH之类的代码进行基准测试。
  2. 您正在尝试对sum上的琐碎计算(Long)进行并行化处理,而不是使用LongStream。如果JVM无法摆脱困境,那么指针追逐的开销很容易使并行性的优势无法承受。
  3. 您正在尝试对iterate产生的固有顺序流进行并行化处理。流框架将通过缓冲流并将其分派到多个线程来尝试执行您要求的操作,这会增加很多开销。
  4. 您在有序并行流上使用limit。这要求流框架进行大量额外的同步,以确保恰好使用n个第一元素来产生结果。您会看到,如果将.unordered()放在并行流中,执行时间将大大减少,但结果不确定,因为您将得到 some n的总和元素,而不一定是 first n元素。

正确的方法是使用JMH并将iterate(...).limit(...)替换为LongStream.rangeClosed(1, n)

答案 1 :(得分:1)

我没有明确讨论基准缺陷(;)。这里的主要问题似乎是对使用特定Stream函数及其行为的理解。

尝试类似的东西:

LongStream.rangeClosed(1, n).parallel().reduce(0L, Long::sum)

但为了公平起见,也应该调整顺序的顺序:

LongStream.rangeClosed(1, n).reduce(0L, Long::sum)

现在我有了运行时行为:

Long Range value - -9223372036854775808 to 9223372036854775807
Time in sequential execution 90 msec with sum = 5000000050000000
Time in parallel execution 25 msec with sum = 5000000050000000

我想这就是您的期望。

与其他所有API一样,您必须了解特定方法在做什么,尤其是如果要并行执行的话。但是正如您所看到的,即使是顺序处理也使用了这种不同的方法。

看看https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps来了解方法的类型。

例如 limit 的用法:

  

同样,本质上与遇到顺序相关的操作   例如limit(),可能需要缓冲以确保正确排序,   破坏并行性的好处