You are on page 1of 5

Лабораторна робота № 2

Паралельнi цикли в OpenMP


програмах

Теоретичнi вiдомостi

Як вже зазначалося ранiше, програмний код блоку директиви parallel за замовчуванням


виконуюється усiма потоками. Даний спосiб може бути корисний, коли потрiбно виконати
однi й тi ж дiї багаторазово. або коли один i той же програмний код може бути застосований
для виконання обробки рiзних даних. Останнiй варiант досить широко використовується
при розробцi паралельних алгоритмiв i програм i зазвичай iменується розпаралелюванням
за даними. У рамках даного пiдходу в OpenMP поряд iз звичайним повторенням в потоках
одного i того ж програмного коду, як у директивi parallel , можна здiйснити подiл iтератив-
но. Така можливiсть є тим бiльш важливою, оскiльки в багатьох випадках саме в циклах
виконується основна частина трудомiстких обчислень. Якщо в паралельнiй областi зустрiв-
ся оператор циклу, то, вiдповiдно до загального правила, вiн буде виконаний усiма потоками
поточної групи, тобто кожен потiк виконає всi iтерацiї даного циклу. Для розпаралелювання
циклiв в OpenMP застосовується директива for:
#pragma omp for [опцiя [[,] опцiя ]...]
Ця директива вiдноситься до блоку, що йде слiдом за даною директивою, котрий мiстить
оператор for.
Слiд зазначити, що для розпаралелювання цикл for повинен мати деякий «канонiчний»1
тип циклу з лiчильником:
for (index = first ; index < end; increment_expr)
Тут index повинен бути цiлочисельною змiнною; на мiсцi знака < у виразi для перевiрки
закiнчення циклу може знаходитися будь-яка операцiя порiвняння <=, > або > =. Операцiя
змiни iтератора циклу повинна мати одну з наступних форм:
• index++, ++index,
• index−−, −−index,
• index+=incr, index−=incr,
• index=index+incr, index=incr+index,
• index=index−incr.
I, звичайно ж, змiннi, котрi використовуються в заголовку оператора циклу, не повиннi
змiнюватися в тiлi циклу, тобто мiж iтерацiями циклу немає iнформацiйної залежностi.
Можливi опцiї:
• private(список) – задає список змiнних, для яких створюється локальна копiя в ко-
жному потоцi; початкове значення локальних копiй змiнних зi списку не визначене;
• firstprivate (список) – теж що i private; початкове значення локальних копiй змiнних
зi списку задається значеннями цих змiнних в основному потоцi;
• lastprivate (список) – змiнним, перерахованим у списку, присвоюється результат, отри-
маний в останнiй iтерацiї циклу;
1
Сенс вимоги «канонiчностi» полягає в тому, щоб на момент початку виконання циклу iснувала можли-
вiсть визначення числа iтерацiй циклу

1
• reduction(оператор: список) – задає оператор i список спiльних змiнних; для кожної
змiнної створюються локальнi копiї в кожному потоцi; локальнi копiї iнiцiалiзуются
вiдповiдно до типу оператора (для адитивних операцiй – 0 або його аналоги, для
мультиплiкативних операцiй – 1 або її аналоги); над локальними копiями змiнних
пiсля виконання всiх операторiв паралельної областi виконується заданий оператор;
оператори для мови Сi – +, −,∗,&,|,^,&&,||, max,min; порядок виконання операторiв
не визначений, тому результат може вiдрiзнятися вiд запуску до запуску;
• schedule(type [, chunk]) – опцiя задає, яким чином iтерацiї циклу розподiляються мiж
потоками;
• collapse(n) – вказує на те, що n щiльно вкладених послiдовних циклiв асоцiюється з
даною директивою; для циклiв утворюється загальний простiр iтерацiй, який дiлиться
мiж потоками; якщо опцiя collapse не задана, то директива стосується тiльки одного
циклу, розташованого за директивою for;
• ordered – опцiя, що говорить про те, що в циклi можуть зустрiчатися директиви
ordered; в цьому випадку визначається блок всерединi тiла циклу, який повинен вико-
нуватися в тому порядку, в якому iтерацiї йдуть в послiдовному циклi;
• nowait – пiсля виконання видiленої дiлянки вiдбувається неявна бар’єрна синхронiза-
цiя паралельно працюючих потокiв: їх подальше виконання вiдбувається тiльки тодi,
коли всi вони досягнуть даної точки; якщо в подiбнiй затримцi немає необхiдностi,
опцiя nowait дозволяє потокам, котрi вже дiйшли до кiнця дiлянки, продовжити ви-
конання без синхронiзацiї з iншими.
Якщо в блоцi директиви parallel немає нiчого, крiм директиви for, то обидвi директиви
можна об’єднати в одну, як у прикладi 2.1.

Приклад 2.1: Розрахунок числа π.


//
#i n c l u d e < s t d i o . h>
double f ( double y ) { r e t u r n ( 4 . 0 / ( 1 . 0 + y∗y ) ) ; }
i n t main ( )
{
double w, x , sum , p i ;
int i ;
int n = 1000000;
w = 1.0/n ;
sum = 0 . 0 ;
#pragma omp p a r a l l e l f o r p r i v a t e ( x ) shared (w) r e d u c t i o n (+:sum)
f o r ( i =0; i < n ; i ++)
{
x = w∗ ( i − 0 . 5 ) ;
sum = sum + f ( x ) ;
}
p i = w∗sum ;
p r i n t f ( " p i ␣=␣%f \n" , p i ) ;
return 0;
}

Управлiння розподiлом iтерацiй циклу мiж потоками

При рiзному обсязi обчислень в рiзних iтерацiях циклу бажано мати можливiсть керу-
вати розподiлом iтерацiй циклу мiж потоками – в OpenMP це забезпечується за допомогою
параметра schedule директиви for. Поле type опцiї schedule може приймати наступнi зна-
чення:
• static – статичний спосiб розподiлу iтерацiй до початку виконання циклу. Якщо по-
ле chunk не вказано, то iтерацiї дiляться порiвну мiж потоками. При заданому зна-

2
ченнi chunk iтерацiї циклу дiляться на блоки розмiру chunk i цi блоки розподiляються
мiж потоками до початку виконання циклу.
• dynamic – динамiчний спосiб розподiлу iтерацiй. До початку виконання циклу по-
токам видiляються блоки iтерацiй розмiру chunk (якщо поле chunk не вказано, то
вважається chunk = 1). Подальше видiлення iтерацiй (також блоками розмiру chunk)
здiйснюється в момент завершення потоками своїх ранiше призначених iтерацiй.
• guided – динамiчний розподiл iтерацiй, при якому розмiр блоку зменшується з деякого
початкового значення до величини chunk (за замовчуванням chunk = 1) пропорцiйно
кiлькостi ще не розподiлених iтерацiй, подiленому на кiлькiсть потокiв, що викону-
ють цикл. Розмiр початкового блоку залежить вiд реалiзацiї. У рядi випадкiв такий
розподiл дозволяє акуратнiше роздiлити роботу i збалансувати завантаження потокiв.
Кiлькiсть iтерацiй в останньому блоцi може виявитися менше значення chunk.
• auto – спосiб розподiлу iтерацiй вибирається компiлятором i/або системою виконання.
Параметр chunk при цьому не задається.
• runtime – спосiб розподiлу iтерацiй вибирається пiд час роботи програми за значен-
ням змiнної середовища OMP_SCHEDULE. Параметр chunk при цьому не задається.
Так, наприклад, для завдання динамiчного способу при розмiрi блоку iтерацiй 3, слiд
визначити:
setenv OMP_SCHEDULE "dynamic,3".
Змiнити значення змiнної OMP_SCHEDULE з програми можна за допомогою викли-
ку функцiї omp_set_schedule().
Приклад 2.2 демонструє використання опцiї schedule з рiзними параметрами. В пара-
лельнiй областi виконується цикл, iтерацiї якого будуть розподiленi мiж iснуючими потока-
ми. На кожнiй iтерацiї буде надруковано, який потiк виконав дану iтерацiю. В тiло циклу
вставлена затримка, що iмiтує деякi обчислення.

Приклад 2.2: Використання опцiї schedule.


#i n c l u d e < s t d i o . h>
#i n c l u d e <omp . h>
i n t main ( i n t argc , char ∗ argv [ ] )
{
int i ;
#pragma omp p a r a l l e l p r i v a t e ( i )
{
#pragma omp f o r s c h ed u l e ( s t a t i c )
// schedule(static, 1)
// schedule(static, 2)
// schedule(dynamic)
// schedule(dynamic, 2)
// schedule(guided)
// schedule(guided, 2)
f o r ( i =0; i <10; i ++)
{
p r i n t f ( " П о т i к ␣%d␣ виконав ␣ i т е р а ц i ю ␣%d\n" ,
omp_get_thread_num ( ) , i ) ;
sleep ( 1 ) ;
}
}
}

Управлiння порядком виконання обчислень

У результатi розпаралелювання циклу порядок виконання iтерацiй не фiксований: за-


лежно вiд стану середовища черговiсть виконання iтерацiй може змiнюватися. Якщо ж

3
для ряду дiй в циклi необхiдно зберегти первинний порядок обчислень, який вiдповiдає
послiдовному виконанню iтерацiй в послiдовнiй програмi, то бажаного результату можна
досягти за допомогою директиви ordered (при цьому для директиви for повинен бути вка-
заний параметр ordered). Пояснимо сказане на фрагментi програми друку сум елементiв
рядкiв матрицi. Розрахунок вiдбуватиметься в деякому довiльному порядку, при необхiдно-
стi друку по порядку розташування рядкiв слiд додати директиву #pragma omp ordered.
#pragma omp p a r a l l e l f o r shared ( a ) p r i v a t e ( i , j , sum ) \
sched u l e ( dynamic , chunk ) ordered
{
f o r ( i =0; i < NMAX; i ++) {
sum = 0 ;
f o r ( j=i ; j < NMAX; j ++)
sum += a [ i ] [ j ] ;
#pragma omp ordered
p r i n t f ( "Сума␣ е л е м е н т i в ␣ рядка ␣%d␣ р i в н а ␣%f \n" , i , sum ) ;
}
}

Пояснимо додатково, що параметр ordered керує порядком виконання тiльки тих дiй,
якi видiленi директивою ordered – виконання решти дiй, що залишилися в iтерацiях циклу
надалi може вiдбуватися паралельно. Важливо пам’ятати також, що директива ordered
може бути застосована в тiлi циклу тiльки один раз.
Слiд зазначити, що необхiднiсть збереження порядку обчислень може призвести до за-
тримок при паралельному виконаннi iтерацiй, що обмежить можливiсть отримання макси-
мального прискорення обчислень.

Функцiї для замiру часу

У OpenMP передбаченi функцiї для роботи з системним таймером.


Функцiя double omp_get_wtime() повертає, в потоцi котрий її викликав, астрономi-
чний час в секундах, що минув з деякого моменту в минулому. Якщо деяку дiлянку про-
грами оточити викликами даної функцiї, то рiзниця повернених значень покаже час роботи
даної дiлянки. Гарантується, що момент часу, використовуваний в якостi точки вiдлiку, не
буде змiнено за час iснування процесу. Таймери рiзних потокiв можуть бути не синхронiзо-
ванi i видавати рiзнi значення.
Функцiя double omp_get_wtick() повертає роздiльну здатнiсть таймера в секундах.
Цей час можна розглядати як мiру точностi таймера.
Приклад 2.3 iлюструє застосування функцiй omp_get_wtime() та omp_get_wtick()
для роботи з таймерами в OpenMP. У даному прикладi проводиться вимiр початкового часу,
потiм вiдразу замiр кiнцевого часу. Рiзниця часiв дає час на замiр часу, також вимiрюється
точнiсть системного таймера.

Приклад 2.3: Робота з системними таймерами.


#i n c l u d e < s t d i o . h>
#i n c l u d e <omp . h>
i n t main ( i n t argc , char ∗ argv [ ] )
{
double start_time , end_time , t i c k ;
start_time = omp_get_wtime ( ) ;
end_time = omp_get_wtime ( ) ;
t i c k = omp_get_wtick ( ) ;
p r i n t f ( "Час␣ на ␣ з а м i р ␣ часу ␣% l f \n" , end_time−start_time ) ;
p r i n t f ( " Т о ч н i с т ь ␣ таймера ␣% l f \n" , t i c k ) ;
}

4
Хiд роботи

1. Ознайомитись з директивою #pragma omp for, та її опцiцями.


2. Написати програму згiдно iндивiдуального завдання. За допомогою функцiї omp_get_wtime()
замiряйте час роботи програми за рiзної кiлькостi потокiв та розмiру вхiдних даних.
Вказiвки: Використати опцiю reduction.
3. Використовуючи опцiю schedule (з рiзними параметрами) модифiкуйте прграму та-
ким чином, щоб на екран виводилось повiдоилення про те, який потiк, яку iтерацiю
виконує.
[<Номер потоку>]: calculation of the iteration number <Номер iтерацiї>.
4. Оформити звiт про виконання лабораторної роботи.

Iндивiдуальнi завдання

Напишiть паралельну OpenMP програму котра:


1. реалiзує пошук максимального значення вектора;
2. знаходить суму елементiв матрицi;
3. знаходить суму максимальних елементiв рядкiв матрицi;
4. знаходить найменше значення суми елементiв рядкiв матрицi;
5. знаходить максимальне значення суми елементiв стовпцiв матрицi;
6. реалiзує додавання квадратних матриць;
7. знаходить суму найменших елементiв стовпцiв матрицi;
8. реалiзує множення квадратних матриць;
9. знаходить максимальний елемент матрицi;
10. знаходить максимальне значення суми елементiв рядкiв матрицi;
11. знаходить суму найменших елементiв рядкiв матрицi;
12. знаходить найменше значення суми елементiв стовпцiв матрицi;
13. реалiзує скалярний добуток двох векторiв;
14. знаходить суму максимальних елементiв стовпцiв матрицi;

You might also like