Паралельне програмування на основі МРІ
В обчислювальних системах з розподіленою пам’яттю процесори функціонують незалежно один від одного. Для організації паралельних обчислень в таких умовах необхідно мати можливість розподіляти обчислювальне навантаження та організувати інформаційну взаємодію (передачу даних) між процесорами. Рішення цих питань забезпечує інтерфейс передачі даних (message passing interface - MPI). В загальному плані, для розподілу обчислень між процесорами необхідно проаналізувати алгоритм розв’язку задачі, виділити інформаційні незалежні фрагменти обчислень, провести їх програмну реалізацію і потім розмістити отримані частини програми на різних процесорах. В рамках МРІ прийнятий простіший підхід - для рішення поставленої задачі розробляється одна програма, яка запускається одночасно на виконання на всіх наявних процесорах. Для уникнення ідентичності обчислень на різних процесорах, можна підставляти різні дані для програми на різних процесорах та використовувати наявні в МРІ засоби для ідентифікації процесора, на якому виконується програма (тим самим надається можливість організувати в обчисленнях в залежності від використовуваного програмою процесора). Такий спосіб організації паралельних обчислень отримав назву моделі "одна програма множина процесів" (single program multiple processes or SPMP).[1]
По-перше, МРІ - це стандарт, якому повинні задовольняти засоби організації передачі повідомлень. По-друге - це програмні засоби, які забезпечують можливості передачі повідомлень і при цьому відповідають всі вимогам стандарту МРІ. Так, за стандартом ці програмні засоби повинні бути організовані у вигляді бібліотек програмних функцій (бібліотека МРІ) і повинні бути доступними для найбільш використовуваних алгоритмічних мов С та Fortran. Подібну "двоїстість" МРІ слід враховувати при використанні термінології. Як правило, абревіатура МРІ застосовується при згадуванні стандарту, а сполучення "бібліотека МРІ" вказує ту чи іншу програмну реалізацію стандарту. Проте достатньо часто для скорочення позначення МРІ використовується для бібліотек МРІ, і, тим самим, для правильної інтерпретації терміну слід враховувати контекст.
Питання, пов’язані з розробкою паралельних програм з використанням МРІ, достатньо добре розглянутий в літературі. Наведемо ряд важливих позитивних обставин:
- МРІ дає змогу в значному ступені знизити гостроту проблеми перенесення паралельних програм між різними компонентами системи - паралельна програма, розроблена на алгоритмічній мові С чи Fortran з використанням бібліотеки МРІ, як правило, працюватиме на різних обчислювальних платформах;
- МРІ сприяє підвищенню ефективності паралельних обчислень, оскільки нині практично для кожного типу обчислювальних систем існують реалізації бібліотек МРІ, які в максимальному ступені враховують можливості комп’ютерного обладнання;
- МРІ зменшує складність розробки паралельних програм, оскільки більша частина раніше нами розглянутих основних операцій передачі даних передбачається стандартом МРІ, з іншого боку, вже є велика кількість бібліотек паралельних методів, створених з використанням МРІ.
Під паралельною програмою в рамках МРІ розуміють множину одночасно виконуваних процесів. Процеси можуть виконуватися на різних процесорах, але на одному процесорі можуть розташовуватися і декілька процесів (в цьому випадку їх виконання здійснюється в режимі розділення часу). В граничному випадку для виконання паралельної програми може використовуватися один процесор - як правило, такий спосіб застосовується для початкової перевірки правильності паралельної програми. Кожний процес програми породжується на основі копії одного і того ж програмного коду (модель SPMP). Цей програмний код, зображуваний у вигляді виконуваної програми, повинен бути доступним в момент запуску паралельної програми на всіх використовуваних процесорах. Вихідний програмний код для виконуваної програми розроблюється на алгоритмічних мовах С чи FORTRAN із застосуванням тієї чи іншої реалізації бібліотеки МРІ. Кількість процесів та чисельність використовуваних процесорів визначається в момент запуску паралельної програми засобами середовища виконання МРІ - програм і в ході обчислень не може змінюватися без застосування спеціальних, але рідко задіяних засобів динамічного породження процесів та управління ними, які з’явилися в стандарті МРІ версії 2.0. Всі процеси програми послідовно перенумеровані від ) до , де є загальна кількість процесорів. Номер процесу іменується рангом процесу.
Основу МРІ складають операції передачі повідомлень. Серед передбачених в складі МРІ функцій розрізняють парні (point-to-point) операції між двома процесами та колективні (collective) комунікаційні дії для одночасної взаємодії декількох процесів. Для виконання парних операцій можна використовувати різні режими передачі, серед яких: синхронний, блокуючий та ін. - повний перелік можливих режимів буде розглянутий далі. До стандарті МРІ включено більшість основних операцій передачі даних.
Процеси паралельної програми об’єднуються в групи. Іншим важливим поняттям МРІ, що описує набір процесів, є поняття комунікатора. Під комунікатором в МРІ розуміють спеціально створюваний службовий об’єкт, який об’єднує в своєму складі групу процесів і ряд додаткових параметрів (контекст), використовуваних при виконанні операцій передачі даних. Парні операції передачі даних виконуються тільки для процесів, які належать одному і тому ж комунікатору. Колективні операції застосовуються одночасно для всіх процесів одного комунікатора. Як результат, вказівка на використовуваний комунікатор є обов’язковою для операцій передачі даних в МРІ. В ході обчислень можуть створюватися нові та видалятися існуючі групи процесів та комунікатори. Один і той же процес може належати різним групам і комунікаторам. Всі наявні в паралельній програмі процеси входять до складу конструйованого за умовчанням комунікатора з ідентифікатором MPI_COMM_WORLD. У версії 2.0 стандарту з’явилася можливість створювати глобальні комунікатори (intercommunicator), які об’єднують в одну структуру пару груп за необхідності виконання колективних операцій між процесами з різних груп. Детальній розгляд можливостей МРІ для роботи з групами і комунікаторами буде даний далі.
При виконанні операцій передачі повідомлень для вказівки даних в функціях МРІ, які передаються чи отримуються, необхідно вказувати тип даних, що пересилаються. МРІ містить великий набір базових типів даних, що багато в чому збігаються з типами даних в алгоритмічних мовах С чи Fortran. Крім того, в МРІ є можливості створення нових похідних типів даних для більш точного і короткого опису вмісту повідомлень, що пересилаються. Детальний розгляд можливостей МРІ для роботи з похідними типами даних буде виконаний далі.
Парні операції передачі даних можна виконати між будь-якими процесами одного і того ж комунікатора, а в колективній операції беруть участь всі процеси комунікатора. Логічна топологія ліній зв’язку між процесами має структуру повного графа (незалежно від наявності реальних фізичних каналів зв’язку між процесорами). Для подальшого викладу і аналізу ряду паралельних алгоритмів доцільним є логічне представлення наявної комунікаційної мережі у вигляді тих чи інших топологій. В МРІ є можливість представлення множини процесів у вигляді решітки довільної розмірності, рис. 2.1. Граничні процеси решіток можуть бути оголошені сусідніми, і, тим самим, на основі решіток можуть бути означені структури типу тор. Крім того, в МРІ є засоби також для формування логічних (віртуальних) топологій будь-якого потрібного типу. Детальний розгляд можливостей МРІ для роботи з топологіями буде здійснений далі. Перед початком розгляду МРІ звернемо увагу на наступні зауваження:
- опис функцій та всі приклади програм, що наводитимуться, будуть представлені на алгоритмічній мові С; особливості використання МРІ для алгоритмічної мови Fortran будуть розглянуті далі;
- коротка характеристика наявних реалізацій бібліотек МРІ та загальний опис середовища виконання МРІ - програм будуть розглянуті далі;
- основне викладення можливостей МРІ буде зорієнтовано на стандарт версії 1.2 (так званий МРІ-1), нововведення стандарту версії 2.0 будуть розглянуті далі.
Приступаючи до вивчення МРІ, можна відмітити, що МРІ є достатньо складним - в стандарті МРІ передбачається наявність більше ніж 120 функцій. З іншого боку, структура МРІ є старанно продуманою - розробка паралельних програм може бути розпочата вже після розгляду 6 функцій МРІ. Всі додаткові можливості МРІ можна засвоїти у міру зростання складності розроблюваних алгоритмів і програм. Виклад матеріалу від простого до складного буде основою подальшого викладу навчального матеріалу.[2]
Наведемо мінімально необхідний набір функцій МРІ, достатній для розро��ки порівняно простих паралельних програм. Ініціалізація та завершення МРІ - програм. Першою функцією МРІ, що викликається, повинна бути функція:
int MPI_Init(int *argc, char ***argv)
,
де - argc - вказівник на кількість параметрів командної стрічки,
- argv - параметри командної стрічки,
яка застосовується для ініціалізації середовища виконання МРІ - програми. Параметрами функції є кількість аргументів в командній стрічці та адреса вказівника на масив символів тексту самої командної стрічки. Останньою функцією МРІ, що викликається, обов’язково повинна бути функція:
int MPI_Finalize (void)
. Як результат, можна відмітити, що структура паралельної програми, розроблена з використанням МРІ, повинна мати наступний вигляд:
# include ’’mpl. h’’
int main(int argc, char *argv[ ]
{ <програмний код без використання функцій МРІ> MPI_Init (&argc, &argv); <програмний код з використанням
функцій МРІ> MPI_Finalize()
; <програмний код без використання функцій МРІ> return 0; } Слід відмітити наступне:
- файл mpi. h містить означення іменованих констант, прототипів функцій та типів даних бібліотеки МРІ;
- функції MPI_Init є обов’язковими і повинні бути виконані (тільки один раз) кожним процесом паралельної програми;
- перед викликом MPI_Init може бути виконана функція MPI_Initialized для означення того, чи був раніше виконаний виклик MPI_Init, а після виклику MPI_Finalized (ця функція з’явилася тільки в стандарті МРІ 2.0) аналогічного призначення.
Розглянуті приклади функцій дають представлення синтаксису іменування функцій в МРІ. Імені функції передує префікс МРІ, далі слідує одне чи декілька слів назви, перше слово в імені функції починається з заголовкового слова, слова розділяються знаком підкреслювання. Назви функцій МРІ, як правило, пояснюють призначення виконуваних функцій дії.
Означення кількості процесів у виконуваній паралельній програмі з використанням функції:
int MPI_size(MPI_Comm, comm int *size)
, де - comm - комунікатор, розмір якого визначається,
-size - кількість процесів в комунікаторі, яка визначається.
Для визначення рангу процесу використовується функція:
int MPI_Com_rank(MPI_Comm comm, int *rank)
, де - - comm - комунікатор, в якому визначається ранг процесу,
- rank - ранг процесу в комунікаторі.
Як правило, виклик функції MPI_Comm_size та MPI_Comm_rank виконується відразу після MPI_Init для отримання більшої кількості процесів і рангу поточного процесу:
# include "mpi . h"
int main(int argc, char *argv[ ]) {
int ProcNum, ProcRank;
<програмний код без використання функцій МРІ>
МРІ_Init(&argc, &argv);
<програмний код з використанням функції МРІ>
MPI_Finalize();
<програмний код без використання функції МРІ>
return 0;
}
Слід відмітити: - файл mp.h містить означення іменованих констант, прототипів функцій і типів даних бібліотеки МРІ;
- функції MPI_Init та MPI_Finalize є обов’язковими і повинні бути виконані (тільки один раз) кожним процесом паралельної програми;
- перед викликом MPI_Init може бути використана функція MPI_Initialize для визначення того, чи був раніше виконаний виклик MPI_Init, а після виклику MPI_Initialize- MPI_Finalized (ця функція з’явилася тільки в стандарті МРІ версії 2.0) аналогічного призначення.
Розглянуті приклади функцій дають уявлення синтаксису іменування функцій в МРІ. Імені функції передує префікс МРІ, далі слідує одне чи декілька слів назви, перше слово в імені функції починається із заголовкового символу, слова розділяються знаком підкреслювання. Назви функції МРІ, як правило, пояснюють призначення виконуваних функцією дій. Визначення кількості і рангу процесів. Визначення кількості у виконуваній паралельній програмі здійснюється з використанням функції:
int MPI_Comm_size(MPI_Comm comm, int *size)
, де - comm - комунікатор, розмір якого визначається , - size - кількість процесів в комунікаторі, що визначається.
Для визначення рангу процесу використовується функція:
int MPI_Comm_rank(MPI_Comm comm, int *rank)
,
де - comm - комунікатор, в якому визначається ранг процесу,
- rank - ранг процесу в комунікаторі.
Як правило, виклик функцій MPI_Comm_size та MPI_Comm_rank виконується зразу після MPI_ Init для отримання загальної кількості процесів і рангу поточного процесу:
# include "mpi h"
int main(int argc, char *argv[ ] {
int ProcNum, ProcRanc;
<програмний код без використання функцій МРІ>
МРІ_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &ProcNum);
MPI_Comm_ranc(MPI_COMM_WORLD, &ProcRank);
<програмний код з використанням функцій МРІ>
МРІ_Finalize();
<програмний код без використання функцій МРІ>
return 0;
}
Слід відмітити:
- комунікатор MPI_COMM_WORLD, як вже зазначалося, створюється за умовчанням і представляє всі процеси виконуваної паралельної програми; - ранг, отриманий з використанням функції MPI_Comm_rank, є рангом процесу, який виконав виклик цієї функції, тобто змінна ProcRank прийме різні значення у різних процесів.
Передача повідомлень. Для передачі повідомлень-відправник повинен виконати функцію:
int MPI_Send(void *buf, int count, MPI_Datatype type, int dest,
int tag, MPI_Comm comm),
де
- buf - адреса буфера пам’яті, в якому розташовані дані відправленого повідомлення;
- count - кількість елементів даних повідомлення;
- type - тип елементів даних повідомлення, що пересилається;
- dest - ранг процесів, якому відправляється повідомлення;
- tag - значення-тег, яке використовується для ідентифікації повідомлення;
- comm - комунікатор, в рамках якого виконується передача даних.[3]