Рассмотрим пример, который определяет средние значения двух соседних элементов массива и записывает результаты в другой массив.
#pragma omp parallel
{
#pragma omp for
for(int i = 1; i < size; ++i)
x[i] = (y[i-1] + y[i+1])/2;
}
Если бы этот код выполнялся на четырехпроцессорном компьютере, а у переменной size было бы значение 100, то выполнение итераций 1—25 могло бы быть поручено первому процессору, 26—50 — второму, 51—75 — третьему, а 76—99 — четвертому. Это характерно для политики планирования, называемой статической.
В конце параллельного региона выполняется барьерная синхронизация (barrier synchronization) - достигнув конца региона, все потоки блокируются до тех пор, пока последний поток не завершит свою работу.
Если из только что приведенного примера исключить директиву #pragma omp for, каждый поток выполнит полный цикл for, проделав много лишней работы:
#pragma omp parallel
{
for(int i = 1; i < size; ++i)
x[i] = (y[i-1] + y[i+1])/2;
}
Так как циклы являются самыми распространенными конструкциями, где выполнение кода можно распараллелить, OpenMP поддерживает сокращенный способ записи комбинации директив #pragma omp parallel и #pragma omp for:
|
|
#pragma omp parallel for
for(int i = 1; i < size; ++i)
x[i] = (y[i-1] + y[i+1])/2;
В качестве более сложного примера рассмотрим сложение одномерных массивов (vector-add program). Массивы A, B, C, переменная N – общие для всех нитей. Переменная I – приватная(private) для каждой нити. Распределение итераций происходит динамически блоками размером CHUNK. Не выполняется синхронизация нитей. Нити завершившие обработку продолжают работу.
C / C++ - Пример использования директивы for #include <omp.h> #define CHUNKSIZE 100 #define N 1000 main () { int i, chunk; float a[N], b[N], c[N]; /* Some initializations */ for (i=0; i < N; i++) a[i] = b[i] = i * 1.0; chunk = CHUNKSIZE; #pragma omp parallel shared(a,b,c,chunk) private(i) { #pragma omp for schedule(dynamic,chunk) nowait for (i=0; i < N; i++) c[i] = a[i] + b[i]; } /* end of parallel section */ } |
Пример использования опции reduction.
- Итерации параллельного цикла распределяются блоками по 10 элементов по нитям.
- В заключении параллельного цикла все нити складывают значения переменной result изменяя ее значение в основной программе.
C / C++ - Опция reduction #include <omp.h> main () { int i, n, chunk; float a[100], b[100], result; /* Some initializations */ n = 100; chunk = 10; result = 0.0; for (i=0; i < n; i++) { a[i] = i * 1.0; b[i] = i * 2.0; } #pragma omp parallel for \ default(shared) private(i) \ schedule(static,chunk) \ reduction(+:result) for (i=0; i < n; i++) result = result + (a[i] * b[i]); printf("Final result= %f\n",result); } |
Restrictions:
- Variables in the list must be named scalar variables. They can not be array or structure type variables. They must also be declared SHARED in the enclosing context.
- Reduction operations may not be associative for real numbers.
- The REDUCTION clause is intended to be used on a region or work-sharing construct in which the reduction variable is used only in statements which have one of following forms:
C / C++ |
x = x op expr x = expr op x (except subtraction) x binop = expr x++ ++x x-- --x |
x is a scalar variable in the list expr is a scalar expression that does not reference xop is not overloaded, and is one of +, *, -, /, &, ^, |, &&, || binop is not overloaded, and is one of +, *, -, /, &, ^, | |