提高欧拉数并行计算的性能

时间:2019-05-18 22:28:22

标签: java multithreading math parallel-processing eulers-number

我正在尝试计算e=∑(3−4k^2/(2k+1)!); k=0..10000 但是我陷入困境,使用多线程无法获得理想的性能提升。

鉴于多个线程,我试图将整个和分成k / numberOfThreads个块,并为每个部分和提交期货。 我认为坏的部分可能是阶乘计算或粒度。我尝试了一个较小的步骤,但是并没有太大的改进。也许需要其他方法。

ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
List<Future<BigDecimal>> futures = new ArrayList<>(numberOfThreads);
int step = k / numberOfThreads ;
BigDecimal result = BigDecimal.ZERO;
for (int j = 0; j <= k; j += step) {
    Future<BigDecimal> future = executor.submit(new EulerCalculator(j, j + step));
    futures.add(future);
}
for (Future<BigDecimal> future : futures) {
    result = result.add(future.get());
}
public class EulerCalculator implements Callable<BigDecimal> {
    private int start;
    private int end;

    public BigDecimal call() {
        long numerator = 3 - 4 * start * start;
        BigDecimal denominator = factorial(2 * start + 1);
        BigDecimal partialSum = BigDecimal.valueOf(numerator)
                                .divide(denominator, 1000, RoundingMode.HALF_EVEN);
        for (int i = start + 1 ; i < end; i++) {
            numerator = 3 - 4 * i * i;
            denominator = denominator.multiply(BigDecimal.valueOf(2 * i * (2*i + 1)));
            partialSum = partialSum.add(BigDecimal.valueOf(numerator)
                                        .divide(fact, 1000, RoundingMode.HALF_EVEN));
        }

        return partialSum;
    }

    private BigDecimal factorial(int cur) {
        BigDecimal fact = BigDecimal.ONE;
        for (int i = 2; i <= cur; i++) {
            fact = fact.multiply(BigDecimal.valueOf(i));
        }

        return fact;
    }
}

在四核上运行几次可获得最佳结果:

k = 10000

线程= 1:345ms

线程= 2:216ms

线程数= 4:184ms

线程数= 8:225ms

3 个答案:

答案 0 :(得分:1)

您的阶乘部分不是恒定时间运算,而是O(n)。这意味着您的第一个线程将比最后一个线程工作少得多。因此,您不能平均分配工作。

通常有三种解决方法。

您可以进行不均匀的步进,即较大的步进用于较小的k。但是,这是非常低效的,因为您要进行数千次相同的乘法运算。

您可以尝试切换到近似算法来计算阶乘,以使其达到恒定时间。对于小k,您可以使用迭代来防止精度损失,因为代价会很低,而且小k也不多。

另一种方法是构建一个包含所有可用于计算的阶乘的大数组,该数组必须在提交任何任务之前运行。这种缓存方法会降低精度。请参阅下面有关如何并行化此过程的评论。

答案 1 :(得分:1)

由于您需要所有denominator,并且每个依赖于 ALL ,因此,我将有一个专用线程来计算所有这些。并为每个计算的denominator提交一个不同的任务到您的线程池,以并行计算特定的部分和。最后,使用parallel stream汇总所有结果。以下代码显示了这些详细信息:

    public static BigDecimal calculate(int k, int numberOfThreads) {
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
        List<Future<BigDecimal>> futures = new ArrayList<>(numberOfThreads);

        BigDecimal denominator = BigDecimal.ONE;
        for (int j = 1; j <= k; j++) {
            denominator = denominator.multiply(BigDecimal.valueOf(4 * j * j + 2 * j));
            Future<BigDecimal> future = executor.submit(computePartialSum(j, denominator));
            futures.add(future);
        }

        return futures.stream().parallel()
            .map(future.get())
            .reduce(BigDecimal.ZERO, BigDecimal::add).add(BigDecimal.valueOf(3));
    }

    public static Callable<BigDecimal> computePartialSum(int curr, BigDecimal denominator) {
        return () -> {
            long numerator = 3 - 4 * curr * curr;
            return BigDecimal.valueOf(numerator).divide(denominator, 1000, RoundingMode.HALF_EVEN);
        };
    }

仍然,您的瓶颈将是阶乘的计算;您可以将其划分为较小的阶乘段,并对其进行缓存以汇总为它们的真实价值,即2美分。

答案 2 :(得分:0)

感谢您的回答! 我用一个简单的for循环缓存了阶乘,对于其他计算,我得到了很好的结果:

1 thread = 17ms
2 threads  = 10ms
4 threads = 7ms

但是我需要绘制与下面的图类似的图,并且只有在我利用线程来计算阶乘时才可能。

enter image description here

我测试了这种n!算法:

public BigDecimal calculate(int number) {
        if (number == 0 || number == 1) {
            return BigDecimal.ONE;
        }
        List<Callable<BigDecimal>> callables = new ArrayList<>();
        int step = number / processors;
        for (int i = 2; i <= number; i += step + 1) {
            callables.add(new FactorialPartCalculator(i, i + step >= number ? number : i + step));
        }
        List<Future<BigDecimal>> futures = executor.invokeAll(callables);
        BigDecimal result = BigDecimal.ONE;
        for (Future<BigDecimal> future : futures) {
            result = result.multiply(future.get());
        }
        return result;
    }
public class FactorialPartCalculator implements Callable<BigDecimal> {
    @Override
    public BigDecimal call() throws Exception {
        BigDecimal factorialPart = BigDecimal.ONE;
        for (int i = start; i <= end; i++) {
            factorialPart = factorialPart.multiply(BigDecimal.valueOf(i));
        }

        return factorialPart;
    }

对于20000!,我有6线程的6.4倍加速。 因此,我需要缓存阶乘,并在整个时间内包括缓存过程。该程序将在32个处理器上进行测试,我应该获得尽可能多的提速

所以我的问题是如何更改上述算法以将所有阶乘存储在数组中?我只需要奇怪的阶乘就可以。