Главная страница
Навигация по странице:

  • 1.4 Реалізація багатопоточності без зайвих зусиль

  • 2. Потоки в OpenMP 2.1 Активізація OpenMP в Visual C ++

  • 2.2. Паралельна обробка в OpenMP

  • 2.3. Конструкції OpenMP

  • 2.4. Реалізація паралельної обробки

  • 2.5. Порівняння підтримки потоків в OpenMP і Win32

  • 3. Модель даних 3.1. Загальні і приватні дані

  • трспо. Лекція Колективні операції обміну повідомленнями в mpi


    Скачать 3.37 Mb.
    НазваниеЛекція Колективні операції обміну повідомленнями в mpi
    Анкортрспо
    Дата23.11.2022
    Размер3.37 Mb.
    Формат файлаpdf
    Имя файлаilovepdf_merged.pdf
    ТипЛекція
    #806999
    страница7 из 12
    1   2   3   4   5   6   7   8   9   ...   12

    1.3 OpenMP і інструментарій
    На даний момент технологія OpenMP підтримується більшістю компіляторів мови Сі / Сі ++. Дещо гірше справа йде з інструментами тестування паралельних OpenMP програм. Інструменти аналізу, перевірки та оптимізації паралельних програм хоча й існують давно, до недавнього часу були мало затребувані при розробці прикладного програмного забезпечення.

    Тому вони часто є менш зручними, ніж інші інструментальні засоби розробки.
    Найбільш повно процес розробки паралельних OpenMP програм підтриманий в пакеті Intel Parallel Studio. Є інструмент попереднього аналізу коду, для виявлення ділянок коду, які потенційно можна ефективно распараллелити. Є добре оптимізуючий компілятор з підтримкою OpenMP. Є профілювальник і інструмент динамічного аналізу для виявлення паралельних помилок.
    Додатково можна виділити інструмент VivaMP, що входить до складу
    PVS-Studio. Це статичний аналізатор коду, спеціалізований на виявленні помилок в OpenMP програмах на етапі їх написання.
    1.4 Реалізація багатопоточності без зайвих зусиль
    Серед фахівців, що займаються паралельними обчисленнями, популярний жарт «Паралельні обчислення – технологія майбутнього ... і так буде завжди». Цей жарт не втрачає актуальності вже кілька десятиліть.
    Аналогічні настрої були поширені в співтоваристві розробників архітектур, стурбованому тим, що скоро буде досягнута межа тактової частоти процесорів, однак частоти процесорів продовжують підвищуватися, хоча набагато повільніше, ніж раніше. Сплав оптимізму фахівців з паралельних обчислень і песимізму архітекторів систем сприяв появі революційних багатоядерних процесорів.
    Головні виробники процесорів змістили акцент з підвищення тактових частот на реалізацію паралелізму в самих процесорах за рахунок використання багатоядерной архітектури. Ідея проста: інтегрувати в один процесор більше одного ядра. Система, що включає процесор з двома ядрами, по суті, не відрізняється від двопроцесорного комп'ютера, а система з чотирьохядерним процесором – від чотирипроцесорні. Цей підхід дозволяє уникнути багатьох технологічних проблем, пов'язаних з підвищенням тактових частот, і створювати при цьому більш продуктивні процесори.
    Все це чудово, але якщо ваше додаток не використовуватиме кілька ядер, його швидкодія ніяк не зміниться. Саме тут і використовується технологія OpenMP, яка допомагає програмістам на C ++ швидше створювати багатопотокові програми.
    OpenMP це дуже об'ємний і потужний API. Слід розглядати цю даний курс як введення, де демонструється застосування різних засобів OpenMP для швидкого написання багатопоточних програм. Якщо знадобиться додаткова
    інформація по цій тематиці, рекомендується звернутися до специфікації, доступною на сайті OpenMP (www.openmp.org), – вона легко читається.
    2. Потоки в OpenMP
    2.1 Активізація OpenMP в Visual C ++
    Стандарт OpenMP був розроблений в 1997 році як API, орієнтований на написання портіруемость багатопоточних додатків. Спочатку він був
    заснований на мові Fortran, але пізніше включив в себе і C / C ++. Остання версія OpenMP - 2.0; її повністю підтримує Visual C ++. Стандарт OpenMP підтримується і платформою Xbox 360.
    Перш ніж займатися кодом, ви повинні знати, як активізувати реалізовані в компіляторі средства OpenMP. Для цього служить параметр компілятора / openmp що з'явився в Visual C ++ 2005. (Ви можете активізувати директиви OpenMP на сторінках властивостей проекту, вибравши Configuration Properties, C / C ++, Language і змінивши значення властивості OpenMP Support.) Зустрівши параметр / openmp, компілятор визначає символ _OPENMP, за допомогою якого можна з'ясувати, чи включені засоби OpenMP. Для цього достатньо написати #ifndef _OPENMP.
    OpenMP зв'язується з додатками через бібліотеку імпорту vcomp.lib.
    Відповідна бібліотека періоду виконання називається vcomp.dll.
    Налагодження версії бібліотек імпорту та періоду виконання (vcompd.lib і vcompd.dll відповідно) підтримують додаткові повідомлення про помилки, що генеруються при деяких неприпустимих операціях. Майте на увазі, що
    Visual C ++ не підтримує статичне зв'язування з бібліотекою OpenMP періоду виконання, хоча в версії для Xbox 360 це підтримується.
    2.2. Паралельна обробка в OpenMP
    Робота OpenMP-програми починається з єдиного потоку – основного. У додатку можуть міститися паралельні регіони, входячи в які, основний потік створює групи потоків (які включають основний потік). В кінці паралельного регіону групи потоків зупиняються, а виконання основного потоку триває. В паралельний регіон можуть бути вкладені інші паралельні регіони, в яких кожен потік початкового регіону стає основним для своєї групи потоків.
    Вкладені регіони можуть в свою чергу включати регіони більш глибокого рівня вкладеності.
    Паралельну обробку в OpenMP ілюструє рис. 1. Найлівіша стрілка представляє основний потік, який виконується на самоті, поки не досягає першого паралельного регіону в точці 1. У цій точці основний потік створює групу потоків, і тепер всі вони одночасно виконуються в паралельному регіоні.

    Рис. 1. Паралельні розділи OpenMP
    У точці 2 три з цих чотирьох потоків, досягнувши вкладеного паралельного регіону, створюють нові групи потоків. Вихідний основний і потоки, які створили нові групи, стають власниками своїх груп (основними в цих групах). Врахуйте, що потоки можуть створювати нові групи в різні моменти або взагалі не зустріти вкладений паралельний регіон.
    У точці 3 вкладений паралельний регіон завершується. Кожен потік вкладеного паралельного регіону синхронізує свій стан з іншими потоками в цьому регіоні, але синхронізація різних регіонів між собою не виконується. У точці 4 закінчується перший паралельний регіон, а в точці 5 починається новий. Локальні дані кожного потоку в проміжках між паралельними регіонами зберігаються.
    Такі основи моделі виконання в OpenMP.
    2.3. Конструкції OpenMP
    OpenMP простий у використанні і включає лише два базових типи конструкцій: директиви pragma і функції середовища, що виконує OpenMP.
    Директиви pragma, як правило, вказують компілятору реалізувати паралельне виконання блоків коду. Всі ці директиви починаються з #pragma omp. Як і будь-які інші директиви pragma, вони ігноруються компілятором, що не підтримують конкретну технологію – в даному випадку OpenMP.

    Функції OpenMP служать в основному для зміни і отримання параметрів середовища. Крім того, OpenMP включає API-функції для підтримки деяких типів синхронізації. Щоб задіяти ці функції бібліотеки
    OpenMP при виконанні програми (у виконуючому середовищи), в програму потрібно включити заголовки omp.h. Якщо ви використовуєте в додатку тільки OpenMP-директиви pragma, включати цей файл не потрібно.
    Для реалізації паралельного виконання блоків додатка потрібно просто додати в код директиви pragma і, якщо потрібно, скористатися функціями бібліотеки OpenMP періоду виконання. Директиви pragma мають такий вигляд:
    #pragma omp <директива> [раздел [ [,] раздел]...]
    OpenMP підтримує директиви parallel, for, parallel for, section, sections, single, master, critical, flush, ordered і atomic, які визначають або механізми поділу роботи або конструкції синхронізації. В нижче розглянемо більшість директив.
    Розділ (clause) – це необов'язковий модифікатор директиви, що впливає на її поведінку. Списки розділів, які підтримуються кожною директивою, розрізняються, а п'ять директив (master, critical, flush, ordered і atomic) взагалі не підтримують розділи.
    2.4. Реалізація паралельної обробки
    Хоча директив OpenMP багато, розглянемо найбільш важливі з них.
    Найважливіша і поширена директива – parallel. Вона створює паралельний регіон для наступного за нею структурованого блоку, наприклад:
    #pragma omp parallel [розділ [[,] розділ] ...] структурований блок
    Ця директива повідомляє компілятору, що структурований блок коду повинен бути виконаний паралельно, в декількох потоках. Кожен потік буде виконувати один і той же потік команд, але не один і той же набір команд - все залежить від операторів, керуючих логікою програми, таких як if-else.
    Як приклад розглянемо класичну програму «Hello World»:
    #pragma omp parallel
    { printf("Hello World\n");
    }
    У двухпроцессорной системі ви, звичайно ж, розраховували б отримати наступне:
    Hello World

    Hello World
    Проте, результат міг би виявитися і таким:
    HellHell oo WorWlodrl d
    Другий варіант можливий через те, що два потоку, які виконуються паралельно, можуть спробувати вивести рядок одночасно. Коли два або більше потоків одночасно намагаються прочитати або змінити загальний ресурс (в нашому випадку їм є вікно консолі), виникає ймовірність гонок
    (race condition). Це недетерміновані помилки в коді програми, знайти які вкрай важко. За для запобігання гонок відповідає програміст; як правило, для цього використовують блокування або зводять до мінімуму звернення до загальних ресурсів.
    Давайте розглянемо більш серйозний приклад, який визначає середні значення двох сусідніх елементів масиву і записує результати в інший масив.
    У цьому прикладі використовується нова OpenMP-конструкція #pragma omp for, яка відноситься до директив поділу роботи (work-sharing directive). Такі директиви застосовуються не для паралельного виконання коду, а для логічного розподілу групи потоків, щоб реалізувати зазначені конструкції керуючої логіки. Директива #pragma omp for повідомляє, що при виконанні циклу for в паралельному регіоні ітерації циклу повинні бути розподілені між потоками групи:
    #pragma omp parallel
    {
    #pragma omp for for(int i = 1; i < size; ++i) x[i] = (y[i-1] + y[i])/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 }
    Так як цикли є найпоширенішими конструкціями, де виконання коду можна распараллелить, OpenMP підтримує скорочений спосіб запису комбінації директив #pragma omp parallel і #pragma omp for:
    #pragma omp parallel for for (int i = 1; i Зверніть увагу, що в цьому циклі немає залежностей, тогбто одна
    ітерація циклу не залежить від результатів виконання інших ітерацій. А ось в двох наступних циклах є два види залежності: for(int i = 1; i <= n; ++i) // цикл 1 a[i] = a[i-1] + b[i]; for(int i = 0; i < n; ++i) // цикл 2 x[i] = x[i+1] + b[i];
    Распараллеліть цикл 1 проблематично тому, що для виконання ітерації i потрібно знати результат ітерації i-1, тобто ітерація i залежить від ітерації
    i-1, а ітерація а[i-1] може бути вирахована як раніш, так і пізніш ніж ітерація
    а [i] якщо ці ітерації вираховуються в різних потоках
    Распараллеліть цикл 2 теж проблематично, але з іншої причини. У цьому циклі ви можете вирахувати значення x [i] до x [i+1], однак, зробивши так, ви більше не зможете обчислити значення x[i+1]. Спостерігається залежність ітерації i+1 від ітерації i. Більш того ітерація x[i+1] може бути вирахована як раніш, так і пізніш ніж ітерація x [i] якщо ці ітерації вираховуються в різних потоках.
    При розпаралелюванні циклів ви повинні переконатися в тому, що
    ітерації циклу не мають залежностей. Якщо цикл не містить залежностей, компілятор може виконувати цикл в будь-якому порядку, навіть паралельно.
    Дотримання цієїо важливї вимоги компілятор не перевіряє – ви самі повинні піклуватися про це. Якщо ви вкажете компілятору распараллелить цикл, що містить залежності, компілятор підкориться, і це призведе до помилки.
    Крім того, OpenMP накладає обмеження на цикли for, які можуть бути включені в блок #pragma omp for або #pragma omp parallel for block. Цикли for повинні відповідати наступним формату:
    for ([цілочисельний тип] i = інваріант циклу; i {<,>, =, <=,> =} інваріант циклу; i {+, -} = інваріант циклу)
    Ці вимоги введені для того, щоб OpenMP міг при вході в цикл визначити число ітерацій.
    2.5. Порівняння підтримки потоків в OpenMP і Win32
    Думаємо, буде корисно порівняти тільки що наведений приклад, що включає директиву #pragma omp parallel for, з кодом, який довелося б написати для вирішення того ж завдання на основі Windows API. Як видно в лістингу 1, для досягнення того ж результату потрібно набагато більше коду, а додатково в цьому варіанті виконуються ще деякі операції. Так, конструктор класу ThreadData визначає, якими мають бути значення start і stop при кожному виклику потоку. OpenMP обробляє всі ці деталі сам і надає програмісту додаткові кошти конфігурації паралельних регіонів і коду.
    Лістинг 1. Багатопоточність в Win32 class ThreadData { public:
    // Конструктор ініціалізує поля start і stop
    ThreadData (int threadNum); int start; int stop;
    };
    DWORD ThreadFn (void * passedInData)
    {
    ThreadData * threadData = (ThreadData *) passedInData; for (int i = threadData-> start; i stop; ++ i) x [i] = (y [i-1] + y [i]) / 2; return 0;
    } void ParallelFor ()
    {
    // Запуск груп потоків for (int i = 0; i ResumeThread (hTeams [i]);
    // Для кожного потоку тут неявно викликається
    // метод ThreadFn

    // Очікування завершення роботи
    WaitForMultipleObjects (nTeams, hTeams, TRUE, INFINITE);
    } int main (int argc, char * argv [])
    {
    // Створення груп потоків for (int i = 0; i {
    ThreadData * threadData = new ThreadData (i); hTeams [i] = CreateThread (NULL, 0, ThreadFn, threadData,
    CREATE_SUSPENDED, NULL);
    }
    ParallelFor (); // імітація OpenMP-конструкції parallel for
    // Очищення for (int i = 0; i CloseHandle (hTeams [i]);
    }
    3. Модель даних
    3.1. Загальні і приватні дані
    Розробляючи паралельні програми, необхідно розуміти, які дані є спільними (shared), а які приватними (private), – від цього залежить не тільки продуктивність, але і коректна робота програми. У OpenMP ця різниця очевидно, до того ж доступ до даних можна налаштувати вручну.
    Спільні змінні доступні всім потокам з групи, тому зміни таких змінних в одному потоці видимі іншим потокам в паралельному регіоні. Що стосується приватних змінних, то кожен потік з групи в своєму розпорядженні їх окремими екземплярами, тому зміни таких змінних в одному потоці ніяк не позначаються на їх примірниках, що належать іншим потокам.
    За замовчуванням всі змінні в паралельному регіоні – спільні, але з цього правила є три винятки. По-перше, приватними є індекси паралельних циклів for. Наприклад, це відноситься до змінної i в коді, показаному в лістингу 2. Змінна j за замовчуванням не є приватною, але явно зроблена такий через розділ firstprivate.
    Лістинг 2. Розділи директив OpenMP і вкладений цикл for float sum = 10.0f;
    MatrixClass myMatrix;
    int j = myMatrix.RowStart (); int i;
    #pragma omp parallel
    {
    #pragma omp for firstprivate (j) lastprivate (i) reduction (+: sum) for (i = 0; i { int doubleI = 2 * i; for (; j { sum + = myMatrix.GetElement (i, j);
    }
    }
    }
    По-друге, приватними є локальні змінні блоків паралельних регіонів.
    У лістингу 2 така змінна doubleI, тому що вона оголошена в паралельному регіоні. Будь-які нестатичні і такі, що не є членами класу MatrixClass, змінні, які оголошені в методі myMatrix :: GetElement, будуть приватними.
    По-третє, приватними будуть будь-які змінні, зазначені в розділах private, firstprivate, lastprivate і reduction. У лістингу 2 змінні i, j і sum зроблені приватними для кожного потоку з групи, т.ч. кожен потік буде розпоряджатися своєю копією кожної з цих змінних.
    Кожен з чотирьох названих розділів приймає список змінних, але семантика цих розділів різниться. Розділ private говорить про те, що для кожного потоку повинна бути створена приватна копія кожної змінної зі списку. Приватні копії будуть инициализироваться значенням за замовчуванням (із застосуванням конструктора за замовчуванням, якщо це доречно). Наприклад, змінні типу int мають за замовчуванням значення 0.
    У розділу firstprivate така ж семантика, але перед виконанням паралельного регіону він вказує копіювати значення приватної змінної з головного потоку в кожен потік, використовуючи конструктор копій, якщо це доречно.
    Семантика розділу lastprivate теж збігається з семантикою розділу private, але при виконанні останньої ітерації циклу або розділу конструкції розпаралелювання значення змінних, зазначених в розділі lastprivate, присвоюються змінним основного потоку. Якщо це доречно, для копіювання об'єктів застосовується оператор присвоювання копій (copy assignment operator).
    Схожа семантика і у розділу reduction, але він приймає змінну і оператор. Підтримувані цим розділом оператори перераховані в табл. 1, а у змінної повинен бути скалярний тип (наприклад, float, int або long, але не std
    :: vector, int [] і т. д.). Змінна розділу reduction инициализируется в кожному потоці значенням, зазначеним у таблиці. В кінці блоку коду оператор розділу reduction застосовується до кожної приватної копії змінної, а також до
    початкового значення змінної (об'єднує в кінці паралельної області приватні значення змінної, вказаної в опції reduction, що отримані в потоках, з використанням зазначеного в опції reduction оператора).
    Табл. 1. Оператори розділу reduction
    Оператор розділу reduction Ініціалізуване (канонічне) значення
    +
    0
    *
    1
    -
    0
    &

    0 (каждый бит установлен)
    |
    0
    ^
    0
    &&
    1
    ||
    0
    У лістингу 2 змінна sum неявно инициализируется в кожному потоці значенням 0.0f (зауважте, що в таблиці вказано канонічне значення 0, але в даному випадку воно приймає форму 0.0f, так як sum має тип float). Після виконання блоку #pragma omp for над усіма приватними значеннями і вихідним значенням sum (яке в нашому випадку дорівнює 10.0f) виконується операція +. Результат присвоюється вихідної загальної змінної sum.
    1   2   3   4   5   6   7   8   9   ...   12


    написать администратору сайта