Проблема воспроизводимости вывода openMP - PullRequest
1 голос
/ 09 апреля 2019

Я прохожу учебные руководства по openMP, и в процессе написания я написал версию кода для openMP, которая вычисляет PI с использованием интеграла.

Я написал серийную версию, так что я знаю, что с серийным аналогом все в порядке.Когда версия openMP была завершена, я заметил, что каждый раз, когда я запускаю ее, она дает мне другой ответ.Если я сделаю несколько прогонов, я увижу, что результаты в целом находятся вокруг правильного числа, но все же я не ожидал, что несколько прогонов openMP дадут разные ответы.

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

void main()

{ int nb=200,i,blob;



 float summ=0,dx,argg;
 dx=1./nb;

 printf("\n dx------------: %f \n",dx);


 omp_set_num_threads(nb);
 #pragma omp parallel
 {

 blob=omp_get_num_threads();

 printf("\n we have now %d number of threads...\n",blob);

 int ID=omp_get_thread_num();
 i=ID;
 printf("\n i is now: %d \n",i);

 argg=(4./(1.+i*dx*i*dx))*dx;
 summ=summ+argg;
 printf("\t\t and summ is %f \n",summ);
 }


 printf("\ntotal summ after loop: %f\n",summ);

 }

Я компилирую этот код в RedHat, используя gcc -f mycode.c -fopenmp, и когда я запускаю его, скажем 3 раза, я получаю:

3.117

3.113

3.051

Может ли кто-нибудь помочь понять, почему я получаю разные результаты?Я делаю что-то неправильно?Параллелизм просто склеивает интервал интегрирования, но когда вычисляются прямоугольники, он должен быть таким же, когда они суммируются в конце, нет?

серийная версия дает мне 3.13

(факт, что я не получаю 3.14, является нормальным, потому что я использовал очень грубую выборку интеграла с 200 делениями между 0 и 1)

Я также пытался добавить барьер, но я все еще получаю разные ответы, хотя и ближе к serial-версии, все же с разбросом в значениях и не идентичны ...

Ответы [ 2 ]

2 голосов
/ 09 апреля 2019

Я считаю, что проблема заключается в объявлении int i и float argg вне параллельного цикла.

Происходит следующее: все ваши 200 потоков перезаписывают i и argg, поэтому иногда argg потока перезаписывается argg из другого потока, что приводит к непредсказуемой ошибке, которую вы наблюдаете.

Вот рабочий код, который всегда печатает одно и то же значение (до 6 десятичных знаков или около того):

void main()
{
    int nb = 200, blob;
    float summ = 0, dx;// , argg;
    dx = 1. / nb;

    printf("\n dx------------: %f \n", dx);

    omp_set_num_threads(nb);
#pragma omp parallel
    {

        blob = omp_get_num_threads();

        printf("\n we have now %d number of threads...\n", blob);

        int i = omp_get_thread_num();
        printf("\n i is now: %d \n", i);

        float argg = (4. / (1. + i * dx*i*dx))*dx;
        summ = summ + argg;
        printf("\t\t and summ is %f \n", summ);
    }

    printf("\ntotal summ after loop: %f\n", summ);
}

Однако изменение последней строки на% .9f показывает, что на самом деле это не то же самое число с плавающей точкой. Это связано с числовыми ошибками при сложении с плавающей запятой. a + b + c не гарантирует тот же результат, что и a + c + b. Вы можете попробовать это в следующем примере:

Сначала добавьте float* arr = new float[nb]; перед параллельным циклом И arr[i] = argg; в параллельном цикле, после определения argg, конечно. Затем добавьте после параллельного цикла:

float testSum = 0;
for (int i = 0; i < nb; i++)
    testSum += arr[i];
printf("random sum: %.9f\n", testSum);

std::sort(arr, arr + nb);
testSum = 0;

for (int i = 0; i < nb; i++)
    testSum += arr[i];
printf("sorted sum: %.9f\n", testSum);

testSum = 0;
for (int i = nb-1; i >= 0; i--)
    testSum += arr[i];
printf("reversed sum: %.9f\n", testSum);

Скорее всего, отсортированная сумма и обратная сумма немного отличаются, даже если они составлены путем сложения одинаковых 200 чисел.

Еще одна вещь, которую вы, возможно, захотите отметить, - это то, что вы вряд ли найдете процессор, который может фактически запускать 200 потоков параллельно. Большинство распространенных процессоров могут обрабатывать от 4 до 32 потоков, в то время как специализированные серверные процессоры могут использовать до 112 потоков с Xeon Platinum 9282. стоимостью $ 15 тыс. *

Поэтому мы обычно делаем следующее:

Удаляем omp_set_num_threads(nb);, чтобы использовать рекомендуемое количество потоков

Мы удаляем int i = omp_get_thread_num();, чтобы использовать int i для цикла for

Переписываем цикл как цикл for:

#pragma omp parallel for
for (int i = 0; i < nb; i++)
    {...}

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

1 голос
/ 09 апреля 2019

Проблема возникает из переменных summ, argg и i. Они относятся к глобальной последовательной области и не могут быть изменены без предосторожностей. Вы будете иметь гонки между потоками, и это может привести к неожиданным значениям в этих переменных. Гонки абсолютно недетерминированы, и это объясняет разные результаты, которые вы получаете. Вы также можете получить правильный результат или любой неправильный результат в зависимости от временных событий чтения и записи в эти переменные.

Правильный способ решения этой проблемы:

  • для переменных argg и i: они объявлены в глобальной области видимости, но используются для выполнения временных вычислений в потоках. Вам следует: либо объявить их в параллельном домене, чтобы сделать их частными, либо добавить private(argg,i) в директиву omp. Обратите внимание, что существует также потенциальная проблема для blob, но ее значение одинаково во всех потоках, и это не должно изменять поведение программы.

  • для переменной summ ситуация иная. Это действительно глобальная переменная, которая накапливает некоторые значения из потоков. Он должен оставаться глобальным, но вы должны добавить директиву atomic openmp при ее изменении. Полная операция чтения-изменения-записи для переменной станет неразрывной, и это обеспечит беспроблемную модификацию.

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

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

void main()

{
  int nb=200,i,blob;
  float summ=0,dx,argg;
  dx=1./nb;

  printf("\n dx------------: %f \n",dx);

  omp_set_num_threads(nb);
# pragma omp parallel private(argg,i)
  {
    blob=omp_get_num_threads();

    printf("\n we have now %d number of threads...\n",blob);

    int ID=omp_get_thread_num();
    i=ID;
    printf("\n i is now: %d \n",i);

    argg=(4./(1.+i*dx*i*dx))*dx;
    #pragma omp atomic
    summ=summ+argg;

    printf("\t\t and summ is %f \n",summ);
  }

  printf("\ntotal summ after loop: %f\n",summ);

}

Как уже отмечалось, это не лучший способ использования потоков. Создание и синхронизация потоков стоит дорого, и редко требуется иметь больше потоков, чем количество ядер.

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