Введення в openmp паралельне програмування на c, intel® software

Не секрет, що «гонка мегагерц», яка протягом багатьох років залишалася головним способом підвищення продуктивності процесорів, змінилася на тенденцію збільшення числа ядер. Грубо кажучи, виробники процесорів навчилися поміщати на одному чіпі кілька процесорів. Зараз практично будь-який комп'ютер обладнаний багатоядерним процесором. Навіть настільні системи початкового рівня мають два ядра - чи варто говорити, що на підході чотирьох- і восьмиядерні системи. Якщо Закон Мура не втратить своєї сили, протягом 5 років середній комп'ютер буде мати 16, або навіть 32 ядра на чіпі.

Проблема полягає в тому, що програмна індустрія поки не встигає за апаратної, і тільки частина додатків здатна ефективно використовувати ресурси багатоядерних процесорів. Кожна програма має один головний потік виконання - набір інструкцій, які виконуються послідовно одна за одною. Природно, в цьому випадку задіяно одне ядро ​​процесора. Програміст повинен подбати про завантаження інших ядер роботою, іншими словами, він повинен зробити так, щоб деякі інструкції виконувалися не послідовно, а одночасно - в паралельному режимі.

Варто відзначити, що продуктивність не зростає лінійно зі збільшенням кількості ядер. Тобто залучення чотирьох ядер не гарантує збільшення продуктивності в чотири рази. Проте, приріст є, і з кожним роком він буде сильніше - з'явиться більше оптимізованих під багатоядерні процесори програм.

Яким чином програмісти можуть управляти потоками, щоб задіяти всю міць процесора? Як зробити так, щоб додаток працювало максимально швидко, масштабувати зі збільшенням числа ядер, і щоб написати такий додаток не було нічним кошмаром програміста? Один з варіантів - вручну створювати потоки в програмному коді, віддавати їм завдання на виконання, потім видаляти. Але в даному випадку потрібно подбати про одну дуже важливу річ - синхронізації. Якщо одному завданню необхідні дані, які обраховуються іншим завданням, ситуація ускладнюється. Важко зрозуміти, що відбувається, коли різні потоки в один і той же час намагаються змінити значення загальних змінних. Та й не хочеться вручну створювати потоки і делегувати їм завдання. На допомогу приходять різні бібліотеки і стандарти для паралельного програмування. Розглянемо докладніше найбільш поширений стандарт для розпаралелювання програм на мовах C, C ++, Fortran - OpenMP.

OpenMP - API, призначене для програмування багатопотокових додатків на багатопроцесорних системах із загальною пам'яттю. Розробку специфікації OpenMP ведуть кілька великих виробників обчислювальної техніки і програмного забезпечення. OpenMP підтримується основними компіляторами.

У OpenMP ви «не побачите» потоки в коді. Замість цього, ви повідомляєте компілятору за допомогою директив #pragma, що блок коду може бути распараллелен. Знаючи цю інформацію, компілятор в стані згенерувати додаток, що складається з одного головного потоку, який створює безліч інших потоків для паралельного блоку коду. Ці потоки синхронізовані в кінці паралельного блоку коду, і ми знову повертаємося до одного головного потоку.


Введення в openmp паралельне програмування на c, intel® software

Використання OpenMP

Так як OpenMP контролюється #pragma, то код на C ++ коректно скомпілюється будь-яким компілятором C ++, тому що не підтримуються #pragma повинні ігноруватися. Однак OpenMP API містить також кілька функцій, і, щоб ними скористатися, необхідно підключити заголовний файл. Найлегший спосіб визначити, чи підтримує компілятор OpenMP - спробувати підключити файл omp.h: #include

Якщо OpenMP підтримується, потрібно його включити за допомогою спеціальних прапорів компілятора:

Директиви OpenMP починаються з #pragma omp.

Дана директива створює групу з N потоків. N визначається під час виконання, зазвичай це число ядер процесора, але також можна задати N вручну. Кожен з потоків в групі виконує наступну за директивою команду (або блок команд, визначений у <>-дужках). Після виконання, потоки «зливаються» в один.

Приклад виведе текст "Hello!" З перенесенням рядка стільки раз, скільки потоків було створено в групі. Для двоядерних систем, текст друкується двічі. (Зауваження: може бути виведено що-небудь типу "HeHlellolo", тому що висновок відбувається паралельно.)

Якщо подивитися, як це влаштовано, то можна побачити, що GCC створює спеціальну функцію і переміщує код блоку в цю функцію, таким чином, всі змінні всередині блоку стають локальними змінними даної функції (відповідно, локальними змінними кожного потоку). З іншого боку, ICC використовує механізм, що нагадує fork (), і не створює спеціальної функції. Обидві реалізації, звичайно, правильні і семантично ідентичні.

Паралелізм може бути умовним, якщо вжити вислів if:

В даному випадку, якщо parallelism_enabled дорівнює нулю, цикл буде оброблений тільки одним (головним) потоком.

Директива for розділяє цикл for між поточною групою потоків, так що кожен потік в групі обробляє свою частину циклу.

Даний цикл виведе числа від 0 до 9 кожне рівно один раз. Однак, порядок їх виведення невідомий. Він може бути, наприклад, таким:


0 5 6 7 1 8 2 3 4 9.

Даний код буде перетворений компілятором в щось подібне:

Таким чином, кожен потік обробляє свою частину циклу паралельно з іншими потоками.

Важливо, що директива #pragma omp лише делегує порції циклу різним потокам в поточній групі потоків. У момент запуску програми група з єдиного (головного) потоку. Щоб створити нову групу потоків, необхідно використовувати ключове слово parallel:

Можна написати коротше:

Обидві записи еквіваленти.

Щоб задати кількість потоків в групі, можна скористатися параметром num_threads:

У OpenMP 2.5, итерационная змінна циклу повинна бути типу signed. У OpenMP 3.0. вона також може мати тип unsigned integer, може бути покажчиком або constant-time random access ітератором. В останньому випадку, для визначення кількості ітерацій циклу буде використовуватися std :: distance ().

Коротке резюме: parallel, for і група потоків

Ще раз відзначимо різницю між parallel, parallel for і for:

    • Група потоків - ті потоки, які виконуються в даний момент.
    • При запуску програми, група містить один потік.
    • Директива parallel розділяє поточний потік в нову групу потоків, поки не буде досягнутий кінець наступного виразу / блоку виразів, потім група потоків зливається в один потік.
    • for розділяє цикл for на частини, і віддає кожну частину потоку з поточної групи. Дана директива НЕ створює нових потоків, вона всього лише ділить робіт між потоками поточної групи.
  • parallel for - короткий запис двох команд: parallel і for. Parallel створює нову групу потоків, потім for роздає кожному потоку з цієї групи частину циклу.

Якщо програма не містить директиви parallel, то вона виконується єдиним потоком.

scheduling (планування)

Програміст може контролювати те, яким чином потоки будуть завантажуватися роботою при обробці циклу. Існує кілька варіантів.

static є варіантом за умовчанням. Ще до входу в цикл кожен потік «знає», які частини циклу він буде обробляти.

Другий варіант - ключове слово dynamic:

В даному випадку неможливо передбачити порядок, в якому ітерації циклу будуть призначені потокам. Кожен потік виконує вказане число ітерацій. Якщо це число не задано, за умовчанням воно дорівнює 1. Після того як потік завершить виконання заданих ітерацій, він переходить до наступного набору ітерацій. Так триває, поки не будуть пройдені всі ітерації. Останній набір ітерацій може бути менше, ніж спочатку заданий. Такий варіант дуже корисний, коли різні ітерації циклу обраховуються різний час. Можна також вказати кількість ітерацій, після виконання яких потік «попросить» у OpeMP наступні:

В даному прикладі, кожен потік виконує три ітерації, потім «бере» наступні три і так далі. Останні, звичайно, можуть мати розмір менше трьох.

Існує також варіант guided. Він схожий на dynamic. але розмір порції зменшується експоненціально. Якщо вказати директиву #pragma omp for schedule (dynamic, 15), цикл for з 100 ітерацій може бути виконаний чотирма потоками в такий спосіб:

А ось яким може виявитися результат виконання того ж циклу чотирма потоками, якщо буде вказана директива #pragma omp for schedule (guided, 15):

Динамічне і кероване планування добре підходять, якщо при кожній ітерації виконуються різні обсяги роботи або якщо одні процесори більш продуктивні, ніж інші. При статичному плануванні немає ніякого способу, що дозволяє збалансувати навантаження на різні потоки. При динамічному і керованому плануванні навантаження розподіляється автоматично - така сама суть цих підходів. Як правило, при керованому плануванні код виконується швидше, ніж при динамічному, внаслідок менших витрат на планування.

ordering (упорядкування)

Порядок, в якому будуть оброблятися ітерації циклу, взагалі кажучи, непередбачуваний. Проте, можливо «змусити» OpenMP виконувати вираження в циклі по порядку. Для цього існує ключове слово ordered:

Цикл «стискає» 100 файлів в паралельному режимі, але «посилає» їх строго в послідовному порядку. Якщо, наприклад, потік «стиснув» сьомий файл, але шостий файл до цього моменту ще не був «відправлений», потік буде очікувати «відправки» шостого файлу. Кожен файл «стискається» і «посилається» один раз, але «стиснення» може відбуватися в паралельному режимі. Дозволено використовувати тільки один ordered блок на цикл.

подальше читання