OpenMP сокращение на элементах контейнера - PullRequest
0 голосов
/ 09 апреля 2020

У меня есть вложенное l oop, с несколькими внешними и многими внутренними итерациями. Во внутреннем l oop мне нужно вычислить сумму, поэтому я хочу использовать сокращение OpenMP. Внешний l oop находится на контейнере, поэтому сокращение должно происходить для элемента этого контейнера. Вот минимальный надуманный пример:

#include <omp.h>
#include <vector>
#include <iostream>

int main(){
    constexpr int n { 128 };

    std::vector<int> vec (4, 0);
    for (unsigned int i {0}; i<vec.size(); ++i){

        /* this does not work */
        //#pragma omp parallel for reduction (+:vec[i])
        //for (int j=0; j<n; ++j)
        //  vec[i] +=j;

        /* this works */
        int* val { &vec[0] };
        #pragma omp parallel for reduction (+:val[i])
        for (int j=0; j<n; ++j)
            val[i] +=j;

        /* this is allowed, but looks very wrong. Produces wrong results
         * for std::vector, but on an Eigen type, it worked. */
        #pragma omp parallel for reduction (+:val[i])
        for (int j=0; j<n; ++j)
            vec[i] +=j;
    }
    for (unsigned int i=0; i<vec.size(); ++i) std::cout << vec[i] << " ";
    std::cout << "\n";

    return 0;
}

Проблема в том, что если я напишу условие сокращения как (+:vec[i]), я получу ошибку ‘vec’ does not have pointer or array type, которая достаточно описательна, чтобы найти обходной путь. Однако это означает, что я должен ввести новую переменную и несколько изменить код logi c, и я нахожу менее очевидным, чтобы увидеть, что код должен делать.

Мой главный вопрос: есть ли это лучший / более чистый / более стандартный способ написать сокращение для элементов контейнера.

Я также хотел бы знать, почему и как работает третий способ, показанный в приведенном выше коде несколько . Я на самом деле работаю с библиотекой Eigen, с контейнерами которой этот вариант работает нормально (хотя я и не тестировал ее всесторонне), но на std::vector она дает результаты где-то между нулем и фактическим результатом (8128 ). Я думал, что это должно работать, потому что vec[i] и val[i] должны оба оценить разыменование одного и того же адреса. Но увы видимо нет.

Я использую OpenMP 4.5 и g cc 9.3.0.

Ответы [ 2 ]

1 голос
/ 11 апреля 2020

Я отвечу на ваш вопрос в трех частях:

1. Как лучше всего выполнить сокращения OpenMP в приведенном выше примере с std::vec?

i) Используйте ваш подход, то есть создайте указатель int* val { &vec[0] };

ii) Объявите новую совместно используемую переменную, такую ​​как @ 1201ProgramAlarm answer.

iii) объявите определяемое пользователем сокращение (что в действительности не применимо в вашем простом случае, но см. ниже 3. для более эффективный шаблон).

2. Почему не работает третий l oop и почему он работает с Eigen?

Как и в предыдущем ответе, вы говорите OpenMP выполнить сокращение суммы для адреса памяти X , но вы выполняете дополнения по адресу памяти Y, что означает, что объявление сокращения игнорируется, и ваше добавление подвергается обычным условиям состязания потоков.

Вы не очень подробно рассказываете о своем предприятии Eigen, но вот некоторые возможные объяснения:

i) Вы на самом деле не используете несколько потоков (отметьте n = Eigen::nbThreads( ))

ii) Вы не отключили собственный параллелизм Эйгена, который может нарушить ваш собственный использование OpenMP, например EIGEN_DONT_PARALLELIZE директива компилятора.

iii) Условие гонки есть, но вы его не видите, потому что операции Eigen занимают больше времени, вы используете небольшое количество потоков и только пишете небольшое количество значений => меньшее количество потоков, мешающих друг другу, чтобы дать неправильный результат.

3. Как мне распараллелить этот сценарий, используя OpenMP (технически это не вопрос, который вы задали явно)?

Вместо распараллеливания только внутреннего l oop, вы должны распараллелить оба в одно и то же время. Чем меньше серийный код у вас есть, тем лучше. В этом сценарии у каждого потока есть своя собственная копия вектора vec, которая уменьшается после суммирования всех элементов их соответствующим потоком. Это решение является оптимальным для вашего представленного примера, но может столкнуться с проблемами ОЗУ, если вы используете очень большой вектор и очень много потоков (или у вас очень ограниченное ОЗУ).

#pragma omp parallel for collapse(2) reduction(vsum : vec)
for (unsigned int i {0}; i<vec.size(); ++i){
    for (int j = 0; j < n; ++j) {
        vec[i] += j;
    }
}

, где vsum - пользователь определенное сокращение, т.е.

#pragma omp declare reduction(vsum : std::vector<int> : std::transform(omp_out.begin(), omp_out.end(), omp_in.begin(), omp_out.begin(), std::plus<int>())) initializer(omp_priv = decltype(omp_orig)(omp_orig.size()))

Объявите сокращение перед функцией, в которой вы его используете, и у вас все будет хорошо до go

0 голосов
/ 09 апреля 2020

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

    int val = vec[i];
    #pragma omp parallel for reduction (+:val)
    for (int j=0; j<n; ++j)
        val +=j;
    vec[i] = val;

С 3-м l oop я подозреваю, что проблема в том, что предложение reduction называет переменную, но вы никогда не обновите эту переменную с этим именем в l oop, поэтому компилятор не видит ничего, что можно уменьшить. Использование Eigen может усложнить анализ кода, что приведет к работе l oop.

...