Паралельне програмування з використанням технології OpenMP
Основні компоненти OpenMPЗ точки зору прикладного програміста до складу середовища OpenMP входять три основних компоненти: • набір директив транслятору; • набір функцій бібліотеки; • змінні оточення; Директиви слугують основним засобом вираження паралелізму в OpenMP. Вони представляють собою спеціальним чином оформлені коментарі (Fortran) або директиви компілятору – «прагми» (C/C++). Це дозволяє ігнорувати такі директиви звичайними трансляторами, які не підтримують OpenMP. В той же час в паралельному варіанті програми можуть з’явитися помилки, які стали результатом некоректного проведеного розпаралелювання. Функцій бібліотеки дозволяють керувати різноманітними характеристиками в процесі виконання OpenMP-програми, наприклад, числом потоків і т. ін. За допомогою змінних оточення користувач має можливість керувати налаштуваннями середовища виконання, наприклад, встановлювати характерні параметри розпаралелювання циклів. Використання змінних оточення дозволяє впливати на поведінку паралельної програми без перекомпіляції.[1] Модель виконання OpenMP-програмиВ OpenMP використовується модель паралельного виконання «розгалуження-злиття» («ветвление-слияние»). Головний (master-thread) потік породжує групу потоків по мірі необхідності. Паралелізм додається інкрементально (покроково). Програма OpenMP починається як єдиний потік виконання, що називається початковим потоком. Коли потік зустрічає паралельну конструкцію, він створює нову групу потоків, що складається із себе та ще декількох додаткових потоків, та стає головним в групі. Всі члени нової групи (включаючи головний) виконують вод всередині паралельної конструкції. В кінці паралельної конструкції є неявний бар’єр. Після паралельної конструкції виконання користувацького коду продовжує лише головний потік. В паралельний регіон можуть бути вкладені інші паралельні регіони, в яких кожен потік початкового регіону стає основним для своєї групи потоків. Вкладені регіони можуть в свою чергу включати регіони більш глибокого рівня вкладеності (рисунок 1.1).[2] ![]() Число потоків в групі, що виконуються паралельно, можна контролювати декількома способами. Один із них – використання змінної оточення OMP_NUM_THREADS. Інший спосіб – виклик процедури omp_set_num_threads(), ще один спосіб – використання виразу num_threads в поєднанні з директивою parallel. Детальніше про це буде йти мова далі.[3] Модель пам’яті OpenMP-програми. Класи зміннихЗмінні в OpenMP-програмі у загальному випадку діляться на два класи: спільні (shared) та індивідуальні (private):
Варто зауважити, що у випадку, якщо зчитування і запис або повторний запис спільної змінної відбувається без синхронізації, то результуюче значення змінної вважається невизначеним. Модель представлено пам’яті OpenMP-програми представлено на рисунку 2.1 ![]() В моделі програмування із спільною пам’яттю в OpenMP більшість змінних за замочуванням вважаються shared (глобальними, спільними). Глобальні змінні спільно використовуються усіма потоками (shared).[4]
Не всі змінні є спільними:
На рисунку 3.2 представлено випадок належності змінних до спільної чи ні пам’яті: ![]() Як видно з рисунку 3.2 змінні Array1, Array2 та count є глобальними відносно паралельної частини програмного коду, а змінні TempArray, iam є локальними змінними, причому на кожному потоці їхнє значення може відрізнятися .Можна змінити клас змінної з допомогою конструкцій:
Конструкція «private(var)» створює локальну копію змінної «var» на кожному з потоків (нитей):
Приклад коду: #pragma omp parallel for private (i,j,sum)
for (i=0; i< m; i++)
{
sum = 0.0;
for (j=0; j< n; j++)
sum +=b[i][j]*c[j];
a[i] = sum;
} Конструкція «firstprivate(var)» є спеціальним випадком «private(var)»: • Ініціалізує кожну приватну копію відповідним значенням з головного потоку. Приклад коду: BOOL FirstTime=TRUE;
#pragma omp parallel for firstprivate(FirstTime)
for (row=0; row<height; row++)
{
if (FirstTime == TRUE) { FirstTime = FALSE; FirstWork (row);
}
AnotherWork (row);
} Конструкція «lastprivate(var)» передає значення приватної змінної, що обчислена на останній ітерації, в глобальну змінну. Приклад коду: int i;
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
[i]=b[i]; /*i == n-1*/ Конструкція «threadprivate(var)» (рисунок 4.3) відрізняється від застосування конструкції «private(var)»:
Конструкція:
![]() Якщо кількість потоків не змінилась, то кожен потік отримає значення, що обчислене в попередній паралельній частині програми. Конструкція DEFAULT змінює клас змінної за замовчуванням:
Компіляція OpenMP-програмиДля компіляції OpenMP-програми потрібно мати відповідні програмні засоби. Такі компілятори представлені в таблиці 1
Є так звана умовна компіляція OpenMP-програми: #include <stdio.h>
int main()
{
#ifdef _OPENMP
printf("Compiled by an OpenMP-compliant implementation.\n");
#endif
return 0;
} В значенні змінної _OPENMP зашифрований рік та місяць (yyyymm) версії стандарту OpenMP, яку підтримує компілятор. І, якщо _OPENMP була визначена, то програмне середовище відкомпілює даний код з використанням OpenMP засобів, а якщо ні - OpenMP засоби використані не будуть, відповідно не екран консольної строки нічого виведено не буде. Примітки
Література
|
Portal di Ensiklopedia Dunia