如何并行化高斯消除算法?

时间:2018-10-19 01:39:45

标签: java c multithreading pthreads gaussian

我被赋予了并行化此算法的任务:

public long GEAlgorithmSequential() {
    long begin = System.nanoTime();

    for (int row = 0; row < size; row++) {
        double value = matrix[row][row];
        for (int col = row + 1; col < size; col++) {
            matrix[row][col] /= value;
        }

        solutionVector[row] /= value;
        matrix[row][row] = 1.0;

        for (int innerRow = row + 1; innerRow < size; innerRow++) {
            double innerValue = matrix[innerRow][row];
            for (int innerCol = row + 1; innerCol < size; innerCol++) {
                //System.out.printf("matrix[%d][%d] (%.2f) -= %.2f * matrix[%d][%d] (%.2f)\n", innerRow, innerCol, matrix[innerRow][innerCol], innerValue, row, innerCol, matrix[row][innerCol]);
                matrix[innerRow][innerCol] -= innerValue * matrix[row][innerCol];
            }
            solutionVector[innerRow] -= matrix[innerRow][row] * solutionVector[row];
            matrix[innerRow][row] = 0.0;
        }
    }

    //PrintMatrix("Upper Triangular Matrix");

    for (int back = size - 1; back >= 0; back--) {
        answers[back] = solutionVector[back];
        for (int i = back - 1; i >= 0; i--) {
            solutionVector[i] -= answers[back] * matrix[i][back];
        }
    }
    return System.nanoTime() - begin;
}

我了解这种算法:第一部分将一行并通过将行中的其他所有内容除以对角线值来使对角线为1。

第二部分,两个for循环,零表示对角线下方的所有内容。

最后一部分,在PrintMatrix调用之后,我们进行反向替换,最终答案矢量位于solutionVector中。

有人告诉我这部分是可并行化的:

for (int innerRow = row + 1; innerRow < size; innerRow++) {
    double innerValue = matrix[innerRow][row];
    for (int innerCol = row + 1; innerCol < size; innerCol++) {
        matrix[innerRow][innerCol] -= innerValue * matrix[row][innerCol];
    }
    solutionVector[innerRow] -= matrix[innerRow][row] * solutionVector[row];
    matrix[innerRow][row] = 0.0;
}

为进一步说明这一部分,它逐行进行,对整行(每列,即内部循环)执行操作。

我的第一个想法是为每一行启动一个线程,因为每一行都是独立的,并且仅依赖于我们刚刚设置为1的主“行”,而我们不会碰它。

所以我做到了:

for (int innerRow = row + 1; innerRow < size; innerRow++) {
    threads[innerRow] = new SubMatrixThread(this, innerRow, row);
    threads[innerRow].start();
}

for (int innerRow = row + 1; innerRow < size; innerRow++) {
    try {
        threads[innerRow].join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

SubMatrixThread是这样的:

@Override
public void run() {
    double innerValue = m.GetMatrix()[innerRow][row];
    for (int innerCol = row + 1; innerCol < size; innerCol++) {
        m.GetMatrix()[innerRow][innerCol] -= innerValue * m.GetMatrix()[row][innerCol];
    }
    m.GetSolutionVector()[innerRow] -= m.GetMatrix()[innerRow][row] * m.GetSolutionVector()[row];
    m.GetMatrix()[innerRow][row] = 0.0;
}

m.GetMatrix()和m.GetSolutionVector()是同步的方法,它们从Matrix对象返回矩阵和向量。

完成所有这些操作后,线程化应用程序所需的时间比顺序执行的时间长得多。例如,在512x512矩阵上,顺序算法花费0.039秒,而线程化花费> 10秒。矩阵越大,时序越差。 IE顺序4098x4098大约需要24秒,并在> 5分钟内完成线程处理(此后我才停止了线程处理)。

有关更多信息:我首先在C中启动了该程序,并遇到了相同的线程化问题(来自pthreads),该过程比顺序执行需要更长的时间。为了弄清楚这一点,我的代码开始变得发疯,因此我用Java编写了代码,以使自己更轻松。

我上面描述的方法为每一行启动一个线程。我也只启动了两个线程,并将内部for循环分为两部分,而不是n部分。我也遇到了同样的问题。

我在Windows桌面上的IntelliJ中运行Java,并且在Linux发行版上运行C程序,这两个应用程序都存在相同的问题。

有人知道我在这里缺少什么吗?

3 个答案:

答案 0 :(得分:1)

您错过了创建线程并启动它的开销。使用线程池。一种简单的创建方法(但还有更多方法,请参见Executors类)

ExecutorService threadPool = Executors.newCachedThreadPool();

您可以提交Runnable实例或Callable实例。

如果只想等待计算完成而又没有从计算中获得返回值,则可以使用Runnable:

Runnable r = ...;
Future<?> f = threadPool.submit(r);

然后稍后要等待结果时,致电

f.get(); 

由于Runnable没有任何内容,因此忽略返回值。

您还可以实现Callable,在计算结束时返回一个值,并在f.get()调用返回的将来使用submit检索该值。

答案 1 :(得分:0)

问题很可能在这里

  

m.GetMatrix()和m.GetSolutionVector()是同步方法

这意味着您为每一个操作调用了同步机制,这会导致巨大的性能损失。

仅使它们成为常规方法就足够了。我没有彻底检查算法,但是您应该没有竞争条件,因为行是独立的。

更不用说它们在不同意义上是没有用的:如果存在竞争风险,则不会受到竞争条件的保护:您获得了矩阵,但是在保护结束后更新了其内容(更新了矩阵)退出这些方法后,您将不再拥有锁。)

答案 2 :(得分:0)

您正在使用太多线程以满足您的需求。仅当您拥有强大的CPU / OS基础结构来支持它们(例如在超级计算机中)时,在应用程序中使用512个线程才有意义。在您的情况下,创建所有这些线程的成本远远超过您要执行这些线程的计算成本。使用线程池并没有多大帮助,因为仍然需要创建这么多线程-好像操作系统中没有512个线程正在等待任何启动的应用程序使用。

要在程序中查看性能优势,您需要在合理数量的线程之间分配工作负载。首先,使其与CPU拥有的内核数量匹配。这样,希望您可以看到它在内核之间均匀分布。