Сокращение OpenMP с помощью Eigen :: VectorXd

Я пытаюсь распараллелить приведенный ниже цикл с сокращением OpenMP;

#define EIGEN_DONT_PARALLELIZE
#include <iostream>
#include <cmath>
#include <string>
#include <eigen3/Eigen/Dense>
#include <eigen3/Eigen/Eigenvalues>
#include <omp.h>

using namespace Eigen;
using namespace std;

VectorXd integrand(double E)
{
    VectorXd answer(500000);
    double f = 5.*E + 32.*E*E*E*E;
    for (int j = 0; j !=50; j++)
        answer[j] =j*f;
    return answer;
}

int main()
{
    omp_set_num_threads(4);
    double start = 0.;
    double end = 1.;
    int n = 100;
    double h = (end - start)/(2.*n);

    VectorXd result(500000);
    result.fill(0.);
    double E = start;
    result = integrand(E);
    #pragma omp parallel
    { 
    #pragma omp for nowait
    for (int j = 1; j <= n; j++){
        E = start + (2*j - 1.)*h;
        result = result + 4.*integrand(E);
        if (j != n){
            E = start + 2*j*h;
            result = result + 2.*integrand(E);
        }
    }
    }
    for (int i=0; i <50 ; ++i)
        cout<< i+1 << " , "<< result[i] << endl;

    return 0;
}

Это определенно быстрее при параллельном подключении, чем без него, но со всеми 4 потоками результаты сильно различаются. Когда количество потоков установлено на 1, вывод правильный. Буду очень признателен, если кто-нибудь сможет мне в этом помочь ...

Я использую компилятор clang с флагами компиляции;

clang++-3.8 energy_integration.cpp -fopenmp=libiomp5

Если это перебор, то мне придется научиться реализовывать Boost::thread, или _4 _...


person AlexD    schedule 08.11.2016    source источник
comment
Добавьте firstprivate(params) reduction(+:result_int) в свою parallel директиву, удалите critical и повторите попытку ...   -  person Gilles    schedule 09.11.2016
comment
@Gilles, спасибо за ваш ответ .. Я отредактировал свой код так, что первый оператор #pragma читает #pragma omp parallel firstprivate(params) reduction(+:result_int), второй оператор #pragma остается как есть, а все последующие #pragma операторы удалены. Затем программа выдает ошибку времени выполнения: ....const Eigen::Matrix<double, -1, 1, 0, -1, 1> >]: Assertion aLhs.rows() == aRhs.rows() && aLhs.cols() == aRhs.cols()' failed. Aborted - Я могу гарантировать, что и kspace, и result_int имеют одинаковое количество элементов и размерность.   -  person AlexD    schedule 09.11.2016
comment
Можете ли вы завершить свой пример до полного минимально воспроизводимого примера? Кроме того, серийная версия работает должным образом?   -  person Avi Ginsburg    schedule 10.11.2016
comment
@AviGinsburg спасибо, пожалуйста, смотрите выше, теперь отредактировано. Да, серийная версия работает как положено   -  person AlexD    schedule 14.11.2016
comment
Это не полностью. Нет определения integrand(...), и ваш код не компилируется. Вы также не ответили, возвращает ли серийная версия правильный результат.   -  person Avi Ginsburg    schedule 14.11.2016
comment
@AviGinsburg, теперь это исправлено, приведенный выше код является очень урезанной версией того, что я пытаюсь выполнить, но я считаю, что ответ поможет моим целям   -  person AlexD    schedule 14.11.2016


Ответы (1)


Ваш код не определяет настраиваемое сокращение для OpenMP, чтобы уменьшить собственные объекты. Я не уверен, поддерживает ли clang определяемое пользователем сокращение (см. OpenMP 4 spec, стр. 180). В таком случае вы можете объявить сокращение и добавить reduction(+:result) в строку #pragma omp for. Если нет, вы можете сделать это самостоятельно, изменив свой код следующим образом:

VectorXd result(500000); // This is the final result, not used by the threads
result.fill(0.);
double E = start;
result = integrand(E);
#pragma omp parallel
{
    // This is a private copy per thread. This resolves race conditions between threads
    VectorXd resultPrivate(500000);
    resultPrivate.fill(0.);
#pragma omp for nowait// reduction(+:result) // Assuming user-defined reductions aren't allowed
    for (int j = 1; j <= n; j++) {
        E = start + (2 * j - 1.)*h;
        resultPrivate = resultPrivate + 4.*integrand(E);
        if (j != n) {
            E = start + 2 * j*h;
            resultPrivate = resultPrivate + 2.*integrand(E);
        }
    }
#pragma omp critical
    {
        // Here we sum the results of each thread one at a time
        result += resultPrivate;
    }
}

Ошибка, которую вы получаете (в вашем комментарии), похоже, связана с размером несоответствие. Хотя в самом вашем коде нет ничего тривиального, не забывайте, что когда OpenMP запускает каждый поток, он должен инициализировать частный VectorXd для каждого потока. Если ничего не указано, по умолчанию будет VectorXd() (с нулевым размером). Когда этот объект используется, возникает несоответствие размеров. «Правильное» использование omp declare reduction будет включать часть инициализатора:

#pragma omp declare reduction (+: VectorXd: omp_out=omp_out+omp_in)\
     initializer(omp_priv=VectorXd::Zero(omp_orig.size()))

omp_priv - это имя частной переменной. Он инициализируется VectorXd::Zero(...). Размер указывается с помощью omp_orig. Стандарт (стр. 182, строки 25-27) определяет это как:

Специальный идентификатор omp_orig также может появиться в предложении инициализатора, и он будет относиться к хранилищу исходной переменной, которая будет уменьшена.

В нашем случае (см. Полный пример ниже) это result. Итак, result.size() равно 500000, и приватная переменная инициализирована до правильного размера.

#include <iostream>
#include <string>
#include <Eigen/Core>
#include <omp.h>

using namespace Eigen;
using namespace std;

VectorXd integrand(double E)
{
    VectorXd answer(500000);
    double f = 5.*E + 32.*E*E*E*E;
    for (int j = 0; j != 50; j++)   answer[j] = j*f;
    return answer;
}

#pragma omp declare reduction (+: Eigen::VectorXd: omp_out=omp_out+omp_in)\
     initializer(omp_priv=VectorXd::Zero(omp_orig.size()))

int main()
{
    omp_set_num_threads(4);
    double start = 0.;
    double end = 1.;
    int n = 100;
    double h = (end - start) / (2.*n);

    VectorXd result(500000);
    result.fill(0.);
    double E = start;
    result = integrand(E);

#pragma omp parallel for reduction(+:result)
    for (int j = 1; j <= n; j++) {
        E = start + (2 * j - 1.)*h;
        result += (4.*integrand(E)).eval();
        if (j != n) {
            E = start + 2 * j*h;
            result += (2.*integrand(E)).eval();
        }
    }
    for (int i = 0; i < 50; ++i)
        cout << i + 1 << " , " << result[i] << endl;

    return 0;
}
person Avi Ginsburg    schedule 14.11.2016
comment
Отлично, спасибо. Это дает мне увеличение скорости в 2,17 раза с 4 потоками. Определенное пользователем сокращение не сработало для меня, но ошибка времени выполнения заставляет меня задуматься, связано ли это с Eigen, а не с Clang. EDIT просто попробовал это с помощью g ++, который даже не компилирует ‘result’ has invalid type for ‘reduction’ - person AlexD; 15.11.2016
comment
Какая ошибка во время выполнения произошла? С каким кодом? Также может быть, что типы Eigen плохо работают с omp, я не пробовал. - person Avi Ginsburg; 15.11.2016
comment
Думаю, вы правы насчет Эйгена и omp. Я протестировал это на коде, который я опубликовал, с вашим определяемым пользователем сокращением. Ошибка выполнения такая же, даже если установлен только один поток, отрывок показан как вывод слишком долго: [BinaryOp = Eigen::internal::scalar_sum_op<double>, Lhs = const Eigen::Matrix<double, -1, 1, 0, -1, 1>, Rhs = const Eigen::CwiseUnaryOp<Eigen::internal::scalar_multiple_op<doub‌​le>, const Eigen::Matrix<double, -1, 1, 0, -1, 1> >]: Assertion `aLhs.rows() == aRhs.rows() && aLhs.cols() == aRhs.cols()' failed. Aborted Спешу добавить, альтернативный метод, который вы описываете, работает очень хорошо. - person AlexD; 16.11.2016
comment
Спасибо за добавление, я продолжу читать по OpenMP из руководства, которое вы ссылаетесь. Я пробовал код, который вы разместили с omp declare reduction, но получаю ошибку компиляции. Clang дает error: expected an OpenMP directive #pragma omp declare reduction(+: VectorXd: omp_out=omp_out+omp_in)\ , а g ++ дает In function ‘int main()’: energy_integral_test.cpp:36:45: error: ‘result’ has invalid type for ‘reduction’ #pragma omp parallel for reduction(+:result). Вы нашли решение, которое мне подходит - person AlexD; 16.11.2016
comment
Вы добавили часть инициализатора? Знак «\» был сделан для того, чтобы очередь не была слишком длинной. - person Avi Ginsburg; 16.11.2016
comment
#pragma omp declare reduction (+: Eigen::VectorXd: omp_out=omp_out+omp_in) initializer(omp_priv=VectorXd::Zero(omp_orig.size())) дает ту же ошибку (это все как одна строка) - person AlexD; 16.11.2016
comment
Похоже, это из старой версии gcc (‹4.9) до поддержки OpenMP4.0. У меня он работает с gcc 5.X как в Ubuntu, так и в Windows (mingw). - person Avi Ginsburg; 16.11.2016
comment
да, я использую gcc 4.8. На данном этапе Clang поддерживает только OpenMP3.1, но я считаю, что OpenMP4.0 и OpenMP4.5 будут реализованы в будущих сборках. - person AlexD; 16.11.2016