【问题标题】:The parallelization takes longer than the non-parallelized task并行化比非并行化任务花费更长的时间
【发布时间】:2019-07-14 15:48:21
【问题描述】:

我需要为大学完成一项编程作业。任务是实现一个程序,它返回方程 q² + 3qp + p² = r² (q,p,r 素数) 的所有解。随后,程序将通过并行化来加速。 不幸的是,我们必须使用 BigInteger,所以不要感到惊讶。

这是我写的课程,它可以精确计算这个方程。

    public boolean calculateEquation() {
    //Equation: p² + 3pq + q² = r²
    if (calculatePSquare().add(calculate3TimesPQ()).add(calculateQSquare()).equals(calculateRSquare())) {
        System.out.println("p: " + p + " q: " + q + " r: " + r);
    }
    return calculatePSquare().add(calculate3TimesPQ()).add(calculateQSquare()).equals(calculateRSquare());
}
@Override
public void run() {
    calculateEquation();
}

该类的完整代码: https://pastebin.com/wwrDurUT

我的下一步是测试代码并停下来看看并行化是否在以后工作。为了实现并行化,我查看了不同的线程,其中还有一个链接在这里:What is the easiest way to parallelize a task in java?

这是我的结果:

ExecutorService executorService = Executors.newFixedThreadPool(Configuration.instance.maximumNumberOfCores);
        ExecutorCompletionService executorCompletionService = new ExecutorCompletionService(executorService);

        for (BigInteger pValue : possiblePValues) {
            for (BigInteger qValue : possibleQValues) {
                for (BigInteger rValue : possibleRValues) {
                    executorCompletionService.submit(new Thirteen(pValue, qValue, rValue), null);
                }
            }
        }
        executorCompletionService.take();

完整代码: https://pastebin.com/kviEnnFH

现在有趣的是,并行化版本只有在任务数量少的情况下才会更快。对于 0-500 之间的所有素数,并行化版本更快。如果我取 0 到 2000 之间的所有素数,结果看起来会很不一样:

0 到 100 之间的所有素数:

未并行化: 任务耗时:110ms

并行化: 任务耗时:64ms

0 到 2000 之间的所有素数:

未并行化: 完成的任务:7797ms

并行化: 完成的任务:25799ms

由于关于该主题的易于理解的资源非常少,而且老实说我不太了解我的代码究竟做了什么,我很惊讶它为什么会这样。

【问题讨论】:

  • 为了排除任何意外,我会检查垃圾收集器在执行期间的行为。数字很​​大,因此您的应用可能大部分时间都花在了 GC 上。
  • 完全没有考虑过GC,你有什么例子可以告诉我如何检查它吗?
  • 试试-XX:+PrintGCJVM参数。

标签: java parallel-processing


【解决方案1】:

首先,使用三重嵌入 for 循环来实现这个p² + 3pq + q² = r² 等式毫无意义,这导致复杂度为O(n) = n^3。这可以仅使用两个 for 循环来完成,因为如果我们知道 pq 的值,则可以使用公式计算 r

for (BigInteger p: possiblePValues) {
    for (BigInteger q: possibleQValues) {
        BigInteger r = p.pow(2).add(q.pow(2)).add(p.multiply(q).multiply(new BigInteger("3")))
        // decide if sqrt(r) is prime
     }
 }

现在,如果我们将预先计算的素数保存在 Hashmap 中,我们可以通过查找立即确定 r 是否为素数。

关于平行化: 您的方法是错误的,因为您只是为一些需要相对较短时间才能完成的操作生成线程。线程管理的开销可能比计算方程本身的操作花费的时间更长。我猜这就是为什么多线程版本的运行时间更长的原因。 关于如何并行化两个嵌入式 for 循环没有黄金法则。我的方法是根据您拥有的核心数量将第一个循环分成更小的块并创建一些间隔。例如,如果我们有 100 个素数和 4 个线程,第一个线程将采用索引从 0 到 24 的 p 值,第二个从 25 到 49 等等。第二个 for 循环应该在每个线程中从 0 运行到 100。使用这种方法,您可以计算所有可能的 r 值。

【讨论】:

  • 我尝试简单地对 q,p,r 的所有可能性进行暴力破解,这是基于我们讲师的指示,您的方法更有意义。我将通过拆分外循环来测试该方法。既然你什么都没说,我认为我的方法调用和我的并行化技巧本身是正确的,还是有更有效的方法?
  • take() 应该被调用多次才能取回结果,并确保所有任务都已完成。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-14
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多