在Java中正确使用并行流

时间:2019-02-24 07:13:35

标签: java java-8 parallel-processing java-stream forkjoinpool

我正在用Java进行并行流的实验,为此,我准备了以下代码来计算n之前的素数。

基本上我有2种方法

  • calNumberOfPrimes(long n)-4种不同的变体
  • isPrime(long n)-2种不同的变体

实际上,上述每种方法都有2个不同的变体,一个变体使用并行流,另一个变体不使用并行流。

    // itself uses parallel stream and calls parallel variant isPrime
    private static long calNumberOfPrimesPP(long n) {
        return LongStream
                .rangeClosed(2, n)
                .parallel()
                .filter(i -> isPrimeParallel(i))
                .count();
    }

    // itself uses parallel stream and calls non-parallel variant isPrime
    private static long calNumberOfPrimesPNP(long n) {
        return LongStream
                .rangeClosed(2, n)
                .parallel()
                .filter(i -> isPrimeNonParallel(i))
                .count();
    }

    // itself uses non-parallel stream and calls parallel variant isPrime
    private static long calNumberOfPrimesNPP(long n) {
        return LongStream
                .rangeClosed(2, n)
                .filter(i -> isPrimeParallel(i))
                .count();
    }

    // itself uses non-parallel stream and calls non-parallel variant isPrime
    private static long calNumberOfPrimesNPNP(long n) {
        return LongStream
                .rangeClosed(2, n)
                .filter(i -> isPrimeNonParallel(i))
                .count();
    }
    // uses parallel stream
    private static boolean isPrimeParallel(long n) {
        return LongStream
                .rangeClosed(2, (long) Math.sqrt(n))
                .parallel()
                .noneMatch(i -> n % i == 0);
    }

    // uses non-parallel stream
    private static boolean isPrimeNonParallel(long n) {
        return LongStream
                .rangeClosed(2, (long) Math.sqrt(n))
                .noneMatch(i -> n % i == 0);
    }

我试图从有效地正确使用并行流的角度,推断出calNumberOfPrimesPPcalNumberOfPrimesPNPcalNumberOfPrimesNPPcalNumberOfPrimesNPNP中哪一个是最好的,以及为什么这是最好的。

我尝试将这4种方法的时间分50次,并使用以下代码取平均值:

    public static void main(String[] args) throws Exception {
        int iterations = 50;
        int n = 1000000;
        double pp, pnp, npp, npnp;
        pp = pnp = npp = npnp = 0;
        for (int i = 0; i < iterations; i++) {
            Callable<Long> runner1 = () -> calNumberOfPrimesPP(n);
            Callable<Long> runner2 = () -> calNumberOfPrimesPNP(n);
            Callable<Long> runner3 = () -> calNumberOfPrimesNPP(n);
            Callable<Long> runner4 = () -> calNumberOfPrimesNPNP(n);

            pp += TimeIt.timeIt(runner1);
            pnp += TimeIt.timeIt(runner2);
            npp += TimeIt.timeIt(runner3);
            npnp += TimeIt.timeIt(runner4);
        }
        System.out.println("___________final results___________");
        System.out.println("avg PP = " + pp / iterations);
        System.out.println("avg PNP = " + pnp / iterations);
        System.out.println("avg NPP = " + npp / iterations);
        System.out.println("avg NPNP = " + npnp / iterations);
    }

TimeIt.timeIt仅返回执行时间(以毫秒为单位)。我得到以下输出:

___________final results___________
avg PP = 2364.51336366
avg PNP = 265.27284506
avg NPP = 11424.194316620002
avg NPNP = 1138.15516624

现在我要对上述执行时间进行推理:

  • PP变体的运行速度不及PNP变体,因为所有并行流都使用公共的fork-join线程池,并且如果我们提交长时间运行的任务,我们将有效地阻塞线程中的所有线程。池。
  • 但是上述参数也应适用于NPP变体,因此NPP变体也应与PNP变体一样快。 (但事实并非如此,就花费的时间而言,NPP变体是最差的)。有人可以解释这个原因吗?

我的问题:

  • 我的推理对PNP变体的小运行时间是否正确?
  • 我想念什么吗?
  • 为什么NPP变体最差(就运行时间而言)?

TimeIt如何测量时间:

class TimeIt {
    private TimeIt() {
    }

    /**
     * returns the time to execute the Callable in milliseconds
     */
    public static <T> double timeIt(Callable<T> callable) throws Exception {
        long start = System.nanoTime();
        System.out.println(callable.call());
        return (System.nanoTime() - start) / 1.0e6;
    }
}

PS:我知道这不是计算素数的最佳方法。 Sieve of Eratosthenes和其他更复杂的方法可以做到这一点。但是通过这个例子,我只想了解并行流的行为以及何时使用它们。

1 个答案:

答案 0 :(得分:5)

很明显,我认为为什么NPP这么慢。

在表格中排列结果数字:

       |    _P    |   _NP
-------+----------+---------
  P_   |   2364   |   265
-------+----------+---------
  NP_  |  11424   |  1138
-------+----------+---------

因此,您看到外部流并行时,它总是更快。这是因为流中有很多工作要做。因此,与要完成的工作相比,处理并行流的额外开销较低。

您还可以看到,内部流不并行时,它总是更快。 isPrimeNonParallelisPrimeParallel快。这是因为流中没有太多工作要做。在大多数情况下,经过几个步骤,即可清楚知道该数字不是素数。一半的数字是偶数(仅一步)。与要完成的工作相比,处理并行流的额外开销很高。