Яка різниця між атомною та критичною у OpenMP?
я можу це зробити
#pragma omp atomic
g_qCount++;
але це не те саме, що
#pragma omp critical
g_qCount++;
?
Яка різниця між атомною та критичною у OpenMP?
я можу це зробити
#pragma omp atomic
g_qCount++;
але це не те саме, що
#pragma omp critical
g_qCount++;
?
Відповіді:
Ефект для g_qCount однаковий, але те, що зроблено, відрізняється.
Критичний розділ OpenMP є повністю загальним - він може оточувати будь-який довільний блок коду. Однак ви платите за цю загальність, проводячи значні накладні витрати щоразу, коли нитка входить і виходить з критичного розділу (крім властивої вартості серіалізації).
(Крім того, у OpenMP всі неназвані критичні секції вважаються однаковими (якщо ви хочете, є лише один замок для всіх неназваних критичних розділів), так що якщо одна нитка знаходиться в одному [безіменному] критичному розділі, як зазначено вище, жодна нитка не може вводити жодну [безіменний] критичний розділ. Як ви можете здогадатися, це можна обійти, використовуючи названі критичні розділи).
Атомна операція має значно нижчі витрати. Якщо є, він користується перевагою обладнання, що забезпечує (скажімо,) операцію збільшення атома; у цьому випадку для введення / виходу з рядка коду немає необхідності блокувати / розблокувати, він просто робить атомний приріст, на який апаратне обладнання говорить, що вам не можна заважати.
Недоліки полягають у тому, що накладні витрати значно нижчі, і одна нитка, яка знаходиться в атомній операції, не блокує жодних (різних) атомних операцій, які вже не відбудуться. Мінусом є обмежений набір операцій, який підтримує атом.
Звичайно, в будь-якому випадку ви несете витрати на серіалізацію.
У OpenMP всі неназвані критичні розділи взаємовиключні.
Найважливіша відмінність між критичною та атомною полягає в тому, що атомний може захищати лише одне призначення, і ви можете використовувати його з конкретними операторами.
Критичний розділ:
Можна поширити на групи послідовних блоків із належним використанням тегу "name".
Повільніше!
Атомна операція:
Значно швидше!
Тільки забезпечує серіалізацію певної операції.
Найшвидший спосіб не є ні критичним, ні атомним. Приблизно, додавання з критичним перерізом в 200 разів дорожче простого додавання, атомне додавання в 25 разів дорожче простого додавання.
Найшвидший варіант (не завжди застосовний) - дати кожному потоку свій лічильник і зробити операцію зменшення, коли вам потрібна загальна сума.
atomic
Важливі обмеження . Вони повинні бути детально описані у специфікаціях OpenMP . MSDN пропонує швидкий шпаргалка, тому що я не здивуюсь, якщо це не зміниться. (Visual Studio 2012 має реалізацію OpenMP з березня 2002 року.) Для цитування MSDN:
Вираз виразу повинен мати одну з таких форм:
x
бінок =expr
x++
++x
x--
--x
У попередніх виразах:
x
цеlvalue
вираз зі скалярним типом.expr
- це вираз зі скалярним типом, і він не посилається на об'єкт, позначенийx
. бінарний оператор не є перевантаженим оператор і є одним з+
,*
,-
,/
,&
,^
,|
,<<
, або>>
.
Я рекомендую використовувати, atomic
коли ви можете і інакше назвати критичні розділи. Назвати їх важливо; ви уникнете налагодження головного болю таким чином.
Тут уже чудові пояснення. Однак ми можемо зануритися трохи глибше. Щоб зрозуміти основну різницю між атомними та критичними розділами в OpenMP, ми повинні спершу зрозуміти концепцію блокування . Давайте розглянемо, чому нам потрібно використовувати замки .
Паралельна програма виконується декількома потоками. Детерміновані результати відбудуться тоді і лише тоді, коли ми виконаємо синхронізацію між цими потоками. Звичайно, синхронізація між потоками не завжди потрібна. Ми маємо на увазі ті випадки, коли синхронізація необхідна.
Для синхронізації потоків у багатопотоковій програмі ми будемо використовувати lock . Коли доступ вимагає обмеження лише однією ниткою одночасно, блокування s вступає в дію. Реалізація концепції блокування може відрізнятися від процесора до процесора. Давайте з’ясуємо, як може просто працювати замок з алгоритмічної точки зору.
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock.
2.2. If lock == 0, lock = 1 and goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
Даний алгоритм можна реалізувати на апаратній мові наступним чином. Ми будемо припускати єдиний процесор і аналізувати поведінку замків у цьому. Для цієї практики припустимо один із таких процесорів: MIPS , Alpha , ARM або Power .
try: LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock
З цією програмою начебто все гаразд, але це не так. Вищевказаний код страждає від попередньої проблеми; синхронізація . Давайте знайдемо проблему. Припустимо, що початкове значення блокування дорівнює нулю. Якщо цей код виконує два потоки, один може дійти до SW R1, заблокувати, перш ніж інший зчитує змінну блокування . Таким чином, вони обидва думають, що замок вільний. Для вирішення цього питання існує ще одна інструкція, а не проста LW та SW . Вона називається інструкцією Read-Modify-Write . Це складна інструкція (що складається з підінструкцій), яка запевняє, що процедуру придбання блокування виконує лише однанитка за раз. Відмінність Read-Modify-Writeпорівняно з простими вказівками для читання та запису є те, що він використовує інший спосіб завантаження та зберігання . Він використовує LL (Load Linked) для завантаження змінної блокування, а SC (Store Conditional) для запису до змінної блокування. Додатковий Реєстр посилань використовується для того, щоб забезпечити процедуру придбання блокування одним потоком. Алгоритм наведений нижче.
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock and put the address of lock variable inside the Link Register.
2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
Коли реєстр посилань буде скинутий, якщо інший потік вважав, що замок вільний, він не зможе знову записати збільшене значення до блокування. Таким чином, паралельність доступу до замка набувається змінної .
Основна різниця між критичною та атомною полягає в тому, що:
Навіщо використовувати locks (нову змінну), тоді як ми можемо використовувати фактичну змінну (яку ми виконуємо над нею), як змінну блокування?
Використання нової змінної для блокування призведе до критичного розділу , тоді як використання фактичної змінної як блокування призведе до атомної концепції. Критичний розділ корисний, коли ми виконуємо велику кількість обчислень (більше одного рядка) на фактичній змінній. Це тому, що якщо результат цих обчислень не вдасться записати на фактичну змінну, для обчислення результатів слід повторити всю процедуру. Це може призвести до низької продуктивності порівняно з очікуванням звільнення блокування перед входом у високо обчислювальний регіон. Таким чином, рекомендується використовувати критичну директиву, коли інтенсивний розділ робиться більш складно обчислювальною областю. атомну директиву всякий раз, коли потрібно виконати єдиний обчислення (x ++, x--, ++ x, --x тощо) та використовувати
atomic - це відносно ефективність роботи, коли вам потрібно включити взаємне виключення лише для однієї інструкції, аналогічна не стосується критичного omp.
atomic - це один критичний розділ твердження, тобто ви блокуєте для виконання одного оператора
критичний розділ - це блокування блоку коду
Хороший компілятор переведе ваш другий код так само, як і перший
++
і*=
) , і що , якщо вони не підтримуються на апаратному рівні , вони можуть бути замінені наcritical
секції.