Forkjoinpool VS 顺序性能

Forkjoinpool VS sequential perofrmance

我正在比较算法(前 n 个数字的总和)的顺序和并行性能(使用 ForkJoinPool):

public class ForkJoinSumCalculator extends RecursiveTask<Long> {
    private static final ForkJoinPool FORKJOINPOOL = new ForkJoinPool();
    private final long[] numbers;
    private final int start;
    private final int end;
    public static final long THRESHOLD = 10_000;       

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        int numLoops = 40;
        for(int i = 1; i <= numLoops; i++) {
            ForkJoinSumCalculator forkJoinSumCalculator = new ForkJoinSumCalculator(LongStream.rangeClosed(1, 100000000).toArray());
            FORKJOINPOOL.invoke(forkJoinSumCalculator);
        }
        System.out.println("Total time parallel:"+ (System.currentTimeMillis() - startTime));

        startTime = System.currentTimeMillis();
        for(int i = 1; i <= numLoops ; i++) {
            long seqSum = 0L;
            for(int j = 1; j <= 100000000 ; j++) {
                seqSum = seqSum + j;
            }
        }
        System.out.println("Total time sequential:"+ (System.currentTimeMillis() - startTime));
    }

    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }
    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        ....splitting the task
        ....or calculating the sum if size is less than THRESHOLD 
    }
}

我尝试改变 numLoops 的值范围很广,但顺序方法总是表现得更好,而且也是按 3-4 的顺序。

考虑到数组大小不是那么小,并行版本应该不会在这里表现得更好。

获得实际输出的一些建议:

1. 对于并行处理,您将在每次循环迭代中创建一个包含 10^8 个元素的长流。在 Java 中,创建一个新对象会做很多事情。因此,它正在影响并行处理的性能。在单个循环中,您根本没有创建任何对象。所以字段不一样不能比较

您可以创建一个实例,并且每次都使用该对象的引用。所以改变这部分:

for(int i = 1; i <= numLoops; i++) {
    ForkJoinSumCalculator forkJoinSumCalculator = new ForkJoinSumCalculator(LongStream.rangeClosed(1, 100000000).toArray());
    FORKJOINPOOL.invoke(forkJoinSumCalculator);
}

对此:

long[] longs = LongStream.rangeClosed(1, 100000000).toArray();
for(int i = 1; i <= numLoops; i++) {
    ForkJoinSumCalculator forkJoinSumCalculator = new ForkJoinSumCalculator(longs);
    FORKJOINPOOL.invoke(forkJoinSumCalculator);
}

2. 当您在顺序任务中进行求和时,您使用了原始变量来声明 seqSum,而您的并行任务正在使用盒装任务来 return.而且盒装计算比原始计算要花更多的时间。

此外,当您在并行任务中发送一个数组时,我猜(您没有在 post 中给出该代码),您正在使用该数组来获取总和。但是在顺序的中,您不需要访问任何引用的索引。相反,您是从迭代变量中获取值。对于10^8这样的数字,其实差很多

所以将那部分从:

for(int i = 1; i <= numLoops ; i++) {
    long seqSum = 0L;
    for(int j = 1; j <= 100000000 ; j++) {
        seqSum = seqSum + j;
    }
}

至:

for(int i = 1; i <= numLoops ; i++) {
    Long seqSum = 0L;
    for(int j = 1; j < 100000000 ; j++) {
        seqSum = seqSum + longs[j];
    }
}

在所有这些更改之后,在一台 4 处理器的机器上,我得到了这个:

Total time parallel:4461
Total time sequential:25542

并使用 8 个处理器的机器(更新了一台配置更好的机器):

Total time parallel:3157
Total time sequential:16863

最后但同样重要的是,你的问题给了我一些思考的要点。这就是为什么,谢谢!

编码愉快!