Как распараллелить алгоритм исключения Гаусса? - PullRequest
0 голосов
/ 19 октября 2018

Мне было поручено распараллелить этот алгоритм:

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, деля все остальное в строке на значение диагонали.

Вторая часть, две для циклов, обнуляет все под диагональю.

Последняя часть, после вызова 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 () синхронизируются метоds, которые возвращают матрицу и вектор из объекта Matrix.

После выполнения всего этого многопоточное приложение занимает намного больше времени, чем последовательное.Например, в матрице 512x512 последовательный алгоритм занимает 0,039 секунды, а многопоточный -> 10 секунд.И время становится все хуже, чем больше матрица.IE sequential 4098x4098 занимает ~ 24 секунды и завершается через> 5 минут (я просто остановил его после этого).

Для получения дополнительной информации: я впервые запустил эту программу на C и столкнулся с той же проблемой многопоточности (от pthreads) занимает больше времени, чем последовательный.Мой код начал сходить с ума от того, что я пытался понять это, поэтому я написал его на Java, чтобы упростить для себя.

Метод, который я описал выше, запускает поток для каждой строки.Я также только начал два потока и разделил внутренний цикл for на две части вместо n частей.Я тоже столкнулся с той же проблемой.

У меня на рабочем столе Windows работает Java в IntelliJ, и я запускал программу C в дистрибутиве Linux, та же проблема в обоих приложениях.

Кто-нибудь знает, что я здесь скучаю?

Ответы [ 3 ]

0 голосов
/ 19 октября 2018

Вам не хватает того, что затраты на создание потока и его запуск значительны.Используйте пул потоков.Простой способ его создания (но есть много других способов, смотрите в классе Executors):

ExecutorService threadPool = Executors.newCachedThreadPool();

Вы можете отправлять экземпляры Runnable или Callable.

Если вы просто хотитедождитесь завершения расчета, не получая возвращаемого значения из расчета, вы можете использовать Runnable:

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

А потом, когда вы хотите дождаться результата, вы вызываете

f.get(); 

Когда вы игнорируете возвращаемое значение, потому что Runnable не имеет его.

Вы также можете реализовать Callable, вернуть значение в конце вычисления и получить значение с помощью f.get() о будущем, возвращенном вызовом submit.

0 голосов
/ 19 октября 2018

Вы используете слишком много потоков для своих нужд.Использование 512 потоков в приложении имело бы смысл только в том случае, если бы у вас была мощная инфраструктура ЦП / ОС для их поддержки (как в суперкомпьютере).В вашем случае стоимость создания всех этих потоков значительно превышает стоимость вычислений, с которыми вы работаете.Использование пула потоков не сильно поможет, потому что это множество потоков все еще нужно создать - это не так, как если бы у ОС было 512 потоков в ожидании для использования любым приложением, которое запускается.

Чтобы увидеть выигрыш в производительностив вашей программе вам нужно распределить рабочую нагрузку между разумным количеством потоков.Для начала, сделайте так, чтобы оно соответствовало количеству ядер вашего процессора.Таким образом, мы надеемся, что вы сможете увидеть его равномерно распределенным между ядрами.

0 голосов
/ 19 октября 2018

По всей вероятности, проблема здесь:

m.GetMatrix () и m.GetSolutionVector () являются синхронизированными методами

Это означает, что для каждогоОперацию вы называете механизмами синхронизации, что приводит к огромной потере производительности.

Достаточно просто сделать их обычными методамиЯ не проверил алгоритм тщательно, но у вас не должно быть условий гонки, потому что строки независимы.

Не говоря уже о том, что они синхронизированы, бесполезно в другом смысле: вы не защищены от условий гонки, если естьбыл риск для них: вы получаете матрицу, но вы обновляете ее содержимое после завершения защиты (вы обновляете матрицу после выхода из этих методов, поэтому у вас больше нет блокировки).

...