Существуют ли обходные пути для использования сокращения OpenMP при добавлении с плавающей запятой? - PullRequest
0 голосов
/ 02 декабря 2018

В настоящее время я работаю над распараллеливанием вложенного цикла for с использованием C ++ и OpenMP.Не вдаваясь в подробности программы, я построил базовый пример понятий, которые я использую ниже:

float var = 0.f;
float distance = some float array;
float temp[] = some float array;
for(int i=0; i < distance.size; i++){
    \\some work
    for(int j=0; j < temp.size; j++){
        var += temp[i]/distance[j]
    }
}

Я попытался распараллелить приведенный выше код следующим образом:

float var = 0.f;
float distance = some float array;
float temp[] = some float array;
#pragma omp parallel for default(shared)
for(int i=0; i < distance.size; i++){
    \\some work
    #pragma omp parallel for reduction(+:var)
    for(int j=0; j < temp.size; j++){
        var += temp[i]/distance[j]
    }
}

Затем я сравнил вывод последовательной программы с выводом параллельной программы и получил неверный результат.Я знаю, что это в основном связано с тем, что арифметика с плавающей точкой не является ассоциативной.Но есть ли обходные пути, которые дают точные результаты?

1 Ответ

0 голосов
/ 03 декабря 2018

Хотя недостаток ассоциативности арифметики с плавающей запятой может быть проблемой в некоторых случаях, код, который вы здесь показывает, представляет гораздо более существенную проблему, которую вам необходимо решить в первую очередь: состояние переменной var во внешнем цикле.

Действительно, поскольку var изменяется внутри цикла i, даже если только в j части цикла i, его необходимо как-то "приватизировать".Теперь точный статус, который нужно получить, зависит от значения, которое вы ожидаете сохранить при выходе из parallel региона:

  • Если вам не важно его значение, просто объявитеэто private (или лучше, объявите его внутри области parallel.
  • Если вам нужно его окончательное значение в конце цикла i, и, учитывая, что оно накапливает сумму значений, скорее всего,вам нужно будет объявить это reduction(+:), хотя lastprivate также может быть тем, что вы хотите (невозможно сказать без дополнительных подробностей)
  • Если private или lastprivate - это все, что вам нужно, но выТакже необходимо указать его начальное значение при входе в область parallel, тогда вам придется подумать и о добавлении firstprivate (в этом нет необходимости, если вы выбрали reduction, поскольку об этом уже позаботились)

Этого должно быть достаточно для исправления вашей проблемы.

Теперь в своем фрагменте вы также распараллелили внутренний цикл. Обычно это плохая идея, чтобы использовать вложенный параллелизм.очень веская причина дляТаким образом, вы, вероятно, получите намного лучшую производительность, только распараллеливая внешний цикл и оставляя внутренний цикл в покое.Это не будет означать, что внутренний цикл не выиграет от распараллеливания, а скорее, что несколько экземпляров внутреннего цикла будут вычисляться параллельно (каждый из которых, по общему признанию, является последовательным, но весь процесс параллелен).Приятным побочным эффектом удаления распараллеливания внутреннего цикла (в дополнение к ускорению работы кода) является то, что теперь все накопления внутри переменных var Privates выполняются в том же порядке, что и не параллельно.Следовательно, ваши (гипотетические) арифметические проблемы с плавающей точкой во внешнем цикле теперь исчезнут, и только если вам понадобится окончательное уменьшение при выходе из области parallel, вы все равно можете столкнуться с ними там.

...