Какие переменные должны быть приватными и / или частными при использовании openMP для распараллеливания кода и когда это уместно? - PullRequest
1 голос
/ 06 июня 2019

Моя задача состоит в том, чтобы распараллелить эту функцию и сделать ее быстрее, чем время последовательного выполнения, но параллель #pragma omp для операторов, которые я пытался выполнить, похоже, не оказывает существенного эффекта.

Последовательная версия этого кода практически идентична, за исключением операторов #pragma. Я понимаю, что код написан очень плохо, это часть задания, цель которого - добиться ускорения в 8 раз. Машина Linux, на которой должен работать код, представляет собой 8-ядерную систему с гиперпоточностью.

Методология тестирования во время выполнения заключается в выводе следующих строк кода:

    clock_gettime(CLOCK_MONOTONIC, &start);
    work_it_par(original, new);
    clock_gettime(CLOCK_MONOTONIC, &finish);

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

void work_it_par(long *old, long *new) {
    int i, j, k;
    int u, v, w;
    long compute_it;
    long aggregate=1.0;
    long weNeedTheFunc = we_need_the_func();
    long gimmieTheFunc = gimmie_the_func();
    int marker = DIM-1;
    #pragma omp parallel for private(i, j, k, compute_it)
    for (i=1; i<marker; i++) {
        for (j=1; j<marker; j++) {
            for (k=1; k<marker; k++) {
                compute_it = old[i*DIM*DIM+j*DIM+k] * weNeedTheFunc;
                aggregate+= compute_it / gimmieTheFunc;
            }
        }
    }
    printf("AGGR:%ld\n",aggregate);
//#pragma omp parallel for private(i, j, u, v)
    for (i=1; i<marker; i++) {
#pragma omp parallel for private(k)
    for (j=1; j<marker; j++) {
        for (k=1; k<marker; k++){
            new[i*DIM*DIM+j*DIM+k]=0;
            for (u=-1; u<=1; u++) {
                for (v=-1; v<=1; v++) {
                    for (w=-1; w<=1; w++) {
                        new[i*DIM*DIM+j*DIM+k]+=old[(i+u)*DIM*DIM+(j+v)*DIM+(k+w)];
                    }
                }
            }
        new[i*DIM*DIM+j*DIM+k]/=27;
      }
    }
  }
#pragma omp parallel for private(i, j)
    for (i=1; i<marker; i++) {
//#pragma omp parallel for private(k)
        for (j=1; j<marker; j++) {
            for (k=1; k<marker; k++) {
                u=(new[i*DIM*DIM+j*DIM+k]/100);
                if (u<=0) u=0;
                if (u>=9) u=9;
                histogrammy[u]++;
             }
         }
    }
}

Ответы [ 3 ]

1 голос
/ 06 июня 2019

Прежде всего, ваш код неверен в многих местах. На первый взгляд я насчитал 7 условий гонки.

Предлагаю использовать следующие общие правила:

  1. Объявите переменные максимально локальными . Это легче сделать правильно, чем пытаться выяснить, какая переменная должна быть закрытой. Это также может помочь объявить переменные как const, чтобы убедиться, что они могут безопасно использоваться совместно.

  2. Если вы суммируете переменную в параллельном цикле, используйте предложение сокращения.

Применение этих принципов к первому циклу выглядит следующим образом:

#pragma omp parallel for reduction(+:aggregate)
for (int i=1; i<marker; i++) {
    for (int j=1; j<marker; j++) {
        for (int k=1; k<marker; k++) {
            long compute_it = old[i*DIM*DIM+j*DIM+k] * weNeedTheFunc;
            aggregate+= compute_it / gimmieTheFunc;
        }
    }
}
  1. Для гистограммы вы также можете использовать reduction(+:histogrammy[:10]) начиная с OpenMP 4.5 или #pragma omp atomic update до операции приращения. Какой из них лучше, зависит от размера - уменьшение массива требует затрат памяти на каждый поток, atomic обновление имеет штраф за конкуренцию.

  2. Как правило, распараллелить самый внешний цикл там, где это безопасно. Для вложенных циклов может быть полезно применить предложение collapse, которое включает несколько циклов в совместном использовании. Помогает ли это, зависит от количества потоков, размера цикла и баланса. Обычно это не больно.

* 1 034 *, например
#pragma omp parallel for collapse(3)
for (int i=1; i < marker; i++) {
    for (int j=1; j < marker; j++) {
        for (int k=1; k < marker; k++) {

Если вы закончили с проверкой правильности кода и хотите посмотреть на производительность, учтите следующее: Используйте инструменты анализа производительности, которые знают OpenMP / потоки. Если вы хотите обсудить фактическую производительность в StackOverflow, вы должны

  1. Включите воспроизводимый пример - включая способ его построения
  2. Опишите вашу конкретную методологию измерения производительности
  3. Включите ваши конкретные результаты измерения производительности
  4. Опишите вашу систему (ЦП, версии компилятора)
0 голосов
/ 07 июня 2019

Я достиг 12x тактовых импульсов со следующим ответом.Вы можете добиться еще более точной оптимизации, преобразовав все три больших цикла в один трехуровневый цикл и установив параллельные прагмы omp внутри цикла для управления потоком.

void work_it_par(long *old, long *new) {
    int i, j, k;
    int i1,j1,k1;
    int u, v, w;
    long compute_it;
    long aggregate=1.0;
    int N = DIM-1;
    int gimme = gimmie_the_func();
    int need = we_need_the_func();
#   pragma omp parallel for private(i, j, k, compute_it) reduction(+:aggregate)     //reduce this part
    for (i=1; i<N; i++)
    {
        for (j=1; j<N; j++)
        {
            for (k=1; k<N; k++)
            {
                compute_it = old[i*DIM*DIM+j*DIM+k] * need;    ///removed the multiplications and divisions
                aggregate += compute_it / gimme;
            }
        }
    }
    //aggregate *= need;
    //aggregate /= gimme;
    
    printf("AGGR:%ld\n",aggregate);
    int increment = 0;
#pragma omp parallel for private(i,j,k,i1,j1,k1) reduction(+:increment)
    for (i=1; i<N; i+=30)
    {
        for (j=1; j<N; j+=30)
        {
            for (k=1; k<N; k+=30)
            {
                for (i1 =i ; i1 < i+30 && i1 < N ; i1++)
                {
                    for (j1 =i ; j1 < i+30 && j1 < N; j1++)
                    {
                        for (k1 =i ; k1 < i+30 && k1 < N ; k1++)
                        {
                            int index = i1*DIM*DIM+j1*DIM+k1;

                            int D = DIM;
                            int DSq = DIM*DIM;
                            increment = 0;
                            increment+=old[index-DSq-D-1];      //-1,-1
                            increment+=old[index-DSq-D];
                            increment+=old[index-DSq-D+1];
                            increment+=old[index-DSq-1];        //-1,0,
                            increment+=old[index-DSq];
                            increment+=old[index-DSq+1];
                            increment+=old[index-DSq+D-1];      //-1,1
                            increment+=old[index-DSq+D];
                            increment+=old[index-DSq+D+1];
                            increment+=old[index-D-1];          //0,-1
                            increment+=old[index-D];
                            increment+=old[index-D+1];
                            increment+=old[index-1];            //0,0
                            increment+=old[index];
                            increment+=old[index+1];
                            increment+=old[index+D-1];          //0,1
                            increment+=old[index+D];
                            increment+=old[index+D+1];
                            increment+=old[index+DSq-D-1];      //1,-1
                            increment+=old[index+DSq-D];
                            increment+=old[index+DSq-D+1];
                            increment+=old[index+DSq-1];        //1,0
                            increment+=old[index+DSq];
                            increment+=old[index+DSq+1];
                            increment+=old[index+DSq+D-1];      //1,1
                            increment+=old[index+DSq+D];
                            increment+=old[index+DSq+D+1];
                            
                            new[index] = increment;
                            
                            new[index]/=27;
                        }
                    }
                }
            }
        }
    }
    int count0,count1,count2,count3,count4,count5,count6,count7,count8,count9 = 0;
#pragma omp parallel for private (i,j,k) reduction(+:count0,count1,count2,count3,count4,count5,count6,count7,count8,count9)
    for (i=1; i<N; i++)
    {
        for (j=1; j<N; j++)
        {
            for (k=1; k<N; k++)
            {
                u=(new[i*DIM*DIM+j*DIM+k]/100);
                if (u<=0) u = 0;
                if (u>=9) u = 9;
                switch (u) {
                    case 0:
                        count0 ++;
                        break;
                    case 1:
                        count1 ++;
                        break;
                    case 2:
                        count2 ++;
                        break;
                    case 3:
                        count3 ++;
                        break;
                    case 4:
                        count4 ++;
                        break;
                    case 5:
                        count5 ++;
                        break;
                    case 6:
                        count6 ++;
                        break;
                    case 7:
                        count7 ++;
                        break;
                    case 8:
                        count8 ++;
                        break;
                    case 9:
                        count9 ++;
                        break;
                }
            }
        }
    }
    histogrammy[0] += count0;
    histogrammy[1] += count1;
    histogrammy[2] += count2;
    histogrammy[3] += count3;
    histogrammy[4] += count4;
    histogrammy[5] += count5;
    histogrammy[6] += count6;
    histogrammy[7] += count7;
    histogrammy[8] += count8;
    histogrammy[9] += count9;
}
0 голосов
/ 06 июня 2019

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

Это происходит в нескольких ситуациях в вашем коде.
compute_it = ... (уже приватная переменная)
агрегат + = ... (особый случай, требующий сокращения)
u = ... (должно быть приватно)
гистограмма [u] + = ... (опять проблема редукции)

Назначение элементам массива также может быть проблемой, но это зависит от индексов. Если индексы зависят от потока и различны в каждом потоке, то это, как правило, правильно, за исключением случаев ложного совместного использования. Это то, что происходит в большинстве ваших назначений для массивов. Например, для new[i*DIM*DIM+j*DIM+k]=... все потоки будут иметь разные значения i (благодаря параллели для), будут затронуты разные части массива, и нет конкретной проблемы с параллелизмом.
Для присвоения histogrammy[u] ситуация другая, так как u зависит от данных и может быть одинаковым в разных потоках. Им можно управлять с уменьшением количества новых версий omp, но в противном случае необходимо выполнить локальное накопление, и в конце потока глобальный массив обновится в должным образом защищенном регионе.

Вот переработанная версия вашего кода (непроверенная, поскольку вы не предоставили рабочий пример). Я также добавил некоторые комментарии и модификации, не связанные с распараллеливанием. Проверьте комментарий с тройкой ///

void work_it_par(long *old, long *new) {
    int i, j, k;
    int u, v, w;
    long compute_it;
    long aggregate=1.0;    //// really?
                           //// I am really surprised that you use a long. 
                           ////   A double seems more appropriate
    long weNeedTheFunc = we_need_the_func();
    long gimmieTheFunc = gimmie_the_func();
    int marker = DIM-1; 
///    #pragma omp parallel for private(i, j, k, compute_it)
#   pragma omp parallel for private(i, j, k, compute_it) reduction(+:aggregate) 
                    /// introduced a reduction on aggregate
    for (i=1; i<marker; i++) {
        for (j=1; j<marker; j++) {
            for (k=1; k<marker; k++) {
                compute_it = old[i*DIM*DIM+j*DIM+k] * weNeedTheFunc;  
                /// aggregate+= compute_it / gimmieTheFunc; /// race on shared var aggregate 
                                                            /// solved by the reduction
                aggregate += compute_it ; /// Unrelated to parallel processing, 
                                          /// but do not do a division in your loop
                                          /// divisions are *expensive* and
                                          /// denominator is always the same
            }
        }
    }
    aggregate /= gimmieTheFunc ; /// now we do the division, but just once
    printf("AGGR:%ld\n",aggregate);
//#pragma omp parallel for private(i, j, u, v)
    for (i=1; i<marker; i++) {
#pragma omp parallel for private(k)
    for (j=1; j<marker; j++) {
        for (k=1; k<marker; k++){
            new[i*DIM*DIM+j*DIM+k]=0;
            for (u=-1; u<=1; u++) {  
                for (v=-1; v<=1; v++) {
                    for (w=-1; w<=1; w++) {
                        new[i*DIM*DIM+j*DIM+k]+=old[(i+u)*DIM*DIM+(j+v)*DIM+(k+w)];
                    }
                }
            }
        new[i*DIM*DIM+j*DIM+k]/=27;
      }
    }
  }
///#pragma omp parallel for private(i, j)
#pragma omp parallel private(i, j, u)  /// parallel region
  {
    int private_histogrammy[10]; /// used to accumulate in the threads
    for (int ii=0; ii<10; ii++) private_histogrammy[ii]=0;
#   pragma omp for             /// a parallel for loop in the parallel region
    for (i=1; i<marker; i++) {
        for (j=1; j<marker; j++) {
            for (k=1; k<marker; k++) {
///                u=(new[i*DIM*DIM+j*DIM+k]/100);
                u=(new[i*DIM*DIM+j*DIM+k]); /// to reduce number of divisions
///                if (u<=0) u=0;
///                if (u>=9) u=9;
///                histogrammy[u]++;
                if (u<=0) private_histogrammy[0]++;
                else if (u>=900) private_histogrammy[9]++;
                else private_histogrammy[u/100]++;
             }
         }
    }
    /// all is done update the global histogrammy
#   pragma omp critical
    /// update the shared array
    /// probably faster with a critical section that updates globally
    /// the (small) array than using atomic on array elements
    /// but alternatives may be tested
    for (int uu=0; uu<10; uu++) histogrammy[uu] += private_histogrammy[uu];
  }  /// end of parallel region
}
...