Интересно, эквивалентны ли эти два цикла по производительности, когда a является глобальным int, а M - int []:
#pragma omp for
for (int i = 0; i < N; ++i) {
#pragma omp atomic
a += M[i];
}
for (int i = 0; i < N; ++i) {
a += M[i];
}
Другими словами, стоит ли его распараллеливать, зная, что единственная инструкция должна быть атомарной? Я лично считаю, что второй цикл невозможно ускорить, потому что изменение никогда не будет выполняться более одного раза одновременно.





Проблема с вашим циклом без синхронизации заключается в том, что результат может быть неверным.
Чтобы ускорить вычисления, вы можете использовать предложение reduction следующим образом:
#pragma omp parallel for reduction(+:a)
Вы легко можете противопоставить эффекту расчетов:
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int main(void)
{
long long int setup = 0, sum_atomic = 0, sum_simple = 0, sum_reduc = 0, sum_noomp = 0;
const int N = 100000;
int *M = malloc(sizeof *M * N);
double tick[5];
for (int i = 0; i < N; ++i) {
M[i] = i;
}
/* setup zone to prevent measuring first parallel zone drawback */
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
setup += M[i];
}
/* using single thread execution */
tick[0] = omp_get_wtime();
for (int i = 0; i < N; ++i) {
sum_noomp += M[i];
}
/* using reduction */
tick[1] = omp_get_wtime();
#pragma omp parallel for reduction(+:sum_reduc)
for (int i = 0; i < N; ++i) {
sum_reduc += M[i];
}
/* using openmp, the wrong way */
tick[2] = omp_get_wtime();
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
sum_simple += M[i];
}
/* using atomic keyword */
tick[3] = omp_get_wtime();
#pragma omp parallel for
for (int i = 0; i < N; ++i) {
#pragma omp atomic
sum_atomic += M[i];
}
tick[4] = omp_get_wtime();
printf("noomp: %lld, in %.0f us\n", sum_noomp, 1000000.*(tick[1]-tick[0]));
printf("reduc: %lld, in %.0f us\n", sum_reduc, 1000000.*(tick[2]-tick[1]));
printf("simple: %lld, in %.0f us\n", sum_simple, 1000000.*(tick[3]-tick[2]));
printf("atomic: %lld, in %.0f us\n", sum_atomic, 1000000.*(tick[4]-tick[3]));
free(M);
return 0;
}
На двухъядерном ЦП результат будет следующим:
noomp: 4999950000, in 28 us -- single thread quite fast
reduc: 4999950000, in 17 us -- reduction: twice fast
simple: 2024135316, in 12 us -- incorrect sum
atomic: 4999950000, in 3686 us -- atomic kw: slow compared to single thread version
Так:
reductionatomicreduction или atomicПодробности:
Скомпилирован с помощью gcc 8.2.1 с опцией -Wall -O3 -mtune=native -march=native -fopenmp на tio машина, с использованием двух ядер процессора Xeon E5-2650 v4.
Для протокола: встреча с самой первой областью parallel обычно требует гораздо более затратной настройки среды OpenMP, чем любые последующие. Следовательно, способ написания вашего теста дает очень несправедливый недостаток первому циклу (тот, который использует atomic). Точно так же, чтобы представить настоящий интерес, ваше сравнение также должно включать время для последовательного кода в качестве ссылки. И, наконец, вы должны указать компилятор и переключатели, которые вы использовали, чтобы позволить другим воспроизвести ваши результаты.
Спасибо, что учли мои комментарии. Теперь последнее слово мудрости: никогда не тестируйте код без полного уровня оптимизации со стороны компилятора, иначе результаты будут бессмысленными. Обычно здесь заменяют ваш -O0 на -O3 -mtune=native -march=native.
@ Жиль: Хорошо, я понял. Во-первых, я отключил оптимизацию, потому что опасаюсь, что компилятор не оптимизирует цикл for ...
Поскольку вы распечатываете результат, этого быть не должно.
Контрольный показатель? Также прочтите статью
reduction().