Проблема с вложенными циклами и openmp - PullRequest
1 голос
/ 20 января 2011

У меня проблемы с применением openmp к вложенному циклу, например:

        #pragma omp parallel shared(S2,nthreads,chunk) private(a,b,tid)
    {
        tid = omp_get_thread_num();
        if (tid == 0)
        {
            nthreads = omp_get_num_threads();
            printf("\nNumber of threads = %d\n", nthreads);
        }
        #pragma omp for schedule(dynamic,chunk)
        for(a=0;a<NREC;a++){
            for(b=0;b<NLIG;b++){
                S2=S2+cos(1+sin(atan(sin(sqrt(a*2+b*5)+cos(a)+sqrt(b)))));
            }
        } // end for a
    } /* end of parallel section */

Когда я сравниваю сериал с версией openmp, последний дает странные результаты.Даже когда я удаляю #pragma omp для, результаты из openmp не верны, вы знаете, почему или можете указать на хороший учебник, явно про двойные циклы и openmp?

Ответы [ 2 ]

9 голосов
/ 21 января 2011

Это классический пример условия гонки . Каждый из ваших потоков openmp одновременно обращается к общему значению и обновляет его, и нет никакой гарантии, что некоторые обновления не будут потеряны (в лучшем случае) или полученный ответ не будет напрасным (в худшем случае).

С условиями гонки дело в том, что они чувствительно зависят от времени; в меньшем случае (например, с меньшими NREC и NLIG) вы можете иногда пропустить это, но в большем случае это в конечном итоге всегда будет появляться.

Причина, по которой вы получаете неправильные ответы без #pragma omp for, заключается в том, что как только вы входите в параллельную область, все ваши потоки openmp запускаются; и если вы не используете что-то вроде omp for (так называемая конструкция разделения рабочих мест), чтобы разделить работу, каждый поток будет делать все в параллельном разделе - так что все потоки будут делать все то же самое сумма, все обновления S2 одновременно.

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

#include <omp.h>
#include <math.h>
#include <stdio.h>

double mysumorig() {

    double S2 = 0;
    int a, b;
    for(a=0;a<128;a++){
        for(b=0;b<128;b++){
            S2=S2+a*b;
        }
    }

    return S2;
}


double mysumallatomic() {

    double S2 = 0.;
#pragma omp parallel for shared(S2)
    for(int a=0; a<128; a++){
        for(int b=0; b<128;b++){
            double myterm = (double)a*b;
            #pragma omp atomic
            S2 += myterm;
        }
    }

    return S2;
}


double mysumonceatomic() {

    double S2 = 0.;
#pragma omp parallel shared(S2)
    {
        double mysum = 0.;
        #pragma omp for
        for(int a=0; a<128; a++){
            for(int b=0; b<128;b++){
                mysum += (double)a*b;
            }
        }
        #pragma omp atomic
        S2 += mysum;
    }
    return S2;
}

int main() {
    printf("(Serial)      S2 = %f\n", mysumorig());
    printf("(All Atomic)  S2 = %f\n", mysumallatomic());
    printf("(Atomic Once) S2 = %f\n", mysumonceatomic());
    return 0;
}

Тем не менее, эта атомарная операция действительно вредит параллельной производительности (в конце концов, все дело в предотвращении параллельной операции вокруг переменной S2!), Поэтому лучшим подходом является суммирование и выполнение только атомарная операция после обоих суммирований вместо 128 * 128 раз; это подпрограмма mysumonceatomic(), которая вызывает накладные расходы на синхронизацию только один раз на поток, а не 16 000 раз на поток.

Но это настолько распространенная операция, что нет необходимости самому ее выполнять. Можно использовать встроенную функциональность OpenMP для операций редукции (редукция - это операция, такая как вычисление суммы списка, нахождение минимума или максимума списка и т. Д., Которая может выполняться только по одному элементу за раз, только глядя на результат пока и следующий элемент), как предложено @ejd. OpenMP будет работать и работать быстрее (его оптимизированная реализация намного быстрее, чем вы можете делать самостоятельно с другими операциями OpenMP).

Как видите, любой подход работает:

$ ./foo
(Serial)      S2 = 66064384.000000
(All Atomic)  S2 = 66064384.000000
(Atomic Once) S2 = 66064384.00000
5 голосов
/ 20 января 2011

Проблема не в двойных циклах, а в переменной S2. Попробуйте наложить на ваше сокращение директиву:

# pragma omp для сокращения расписания (динамического, чанка) (+: S2)

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