В чем разница между атомарным и критическим в 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 все безымянные критические секции считаются идентичными (если вы предпочитаете, есть только одна блокировка для всех безымянных критических секций), так что если один поток находится в одной [безымянной] критической секции, как указано выше, ни один поток не может войти в какую-либо [безымянный] критический раздел. Как вы могли догадаться, это можно обойти, используя именованные критические разделы).
Атомарная операция имеет гораздо меньшие накладные расходы. Там, где это возможно, он использует преимущества оборудования, обеспечивающего (скажем) операцию атомарного приращения; в этом случае нет необходимости в блокировке / разблокировке при входе / выходе из строки кода, он просто выполняет атомарное приращение, которое, по словам аппаратного обеспечения, вам нельзя мешать.
Плюсы в том, что накладные расходы намного ниже, и один поток, выполняющий атомарную операцию, не блокирует какие-либо (разные) атомные операции, которые могут произойти. Обратной стороной является ограниченный набор операций, которые поддерживает atomic.
Конечно, в любом случае вы несете расходы на сериализацию.
В OpenMP все безымянные критические разделы являются взаимоисключающими.
Наиболее важное различие между критическим и атомарным является то, что атомарный может защитить только одно присвоение, и вы можете использовать его с определенными операторами.
Критический раздел:
Может быть расширен для сериализации групп блоков при правильном использовании тега «name».
помедленнее!
Атомарная операция:
Намного быстрее!
Обеспечивает только сериализацию определенной операции.
Самый быстрый способ не является ни критическим, ни атомарным. Примерно добавление с критическим сечением в 200 раз дороже простого добавления, атомарное добавление в 25 раз дороже простого добавления.
Самый быстрый вариант (не всегда применимый) - дать каждому потоку свой собственный счетчик и выполнить операцию сокращения, когда вам нужна общая сумма.
Ограничения atomic
важны. Они должны быть подробно описаны в спецификациях OpenMP . MSDN предлагает краткую шпаргалку, так как я не удивлюсь, если это не изменится. (Visual Studio 2012 имеет реализацию OpenMP с марта 2002 г.) Процитируем MSDN:
Оператор выражения должен иметь одну из следующих форм:
x
binop =expr
x++
++x
x--
--x
В предыдущих выражениях:
x
-lvalue
выражение скалярного типа.expr
является выражением скалярного типа, и оно не ссылается на объект, обозначенныйx
. бинарный оператор не является перегруженным оператор и является одним из+
,*
,-
,/
,&
,^
,|
,<<
, или>>
.
Я рекомендую использовать, atomic
когда вы можете, и иначе называли критические разделы. Назвать их важно; таким образом вы избежите головной боли отладки.
Здесь уже отличные объяснения. Однако мы можем погрузиться немного глубже. Чтобы понять основную разницу между концепциями атомарного и критического разделов в OpenMP, мы должны сначала понять концепцию блокировки . Давайте рассмотрим, почему нам нужно использовать блокировки .
Параллельная программа выполняется несколькими потоками. Детерминированные результаты будут достигнуты тогда и только тогда, когда мы выполним синхронизацию между этими потоками. Конечно, синхронизация между потоками требуется не всегда. Речь идет о тех случаях, когда синхронизация необходима.
Чтобы синхронизировать потоки в многопоточной программе, мы будем использовать lock . Когда требуется ограничить доступ только одним потоком за раз, в игру вступают блокировки . Реализация концепции блокировки может варьироваться от процессора к процессору. Давайте выясним, как может работать простая блокировка с алгоритмической точки зрения.
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, lock до того, как другой прочитает переменную блокировки . Таким образом, они оба думают, что замок свободный. Чтобы решить эту проблему, есть другая инструкция, а не простые LW и SW . Это называется инструкцией чтения-изменения-записи . Это сложная инструкция (состоящая из подинструкций), которая гарантирует, что процедура получения блокировки выполняется только одним поток за раз. Отличие команд чтения-изменения-записи от простых инструкций чтения и записи состоит в том, что в нем используется другой способ загрузки и сохранения.. Он использует 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
Когда регистр ссылки сбрасывается, если другой поток предположил, что блокировка свободна, он не сможет снова записать увеличенное значение в блокировку. Таким образом достигается одновременный доступ к переменной блокировки .
Основное различие между критическим и атомарным заключается в том, что:
Зачем использовать блокировки (новую переменную), когда мы можем использовать фактическую переменную (с которой мы выполняем операцию) в качестве переменной блокировки?
Использование новой переменной для блокировок приведет к критическому разделу , в то время как использование фактической переменной в качестве блокировки приведет к критической директиве, когда более сложная в вычислительном отношении область выполняется интенсивным разделом. атомарной концепции. Критический раздел полезен, когда мы выполняем много вычислений (более одной строки) над фактической переменной. Это потому, что, если результат этих вычислений не может быть записан в фактическую переменную, всю процедуру следует повторить для вычисления результатов. Это может привести к снижению производительности по сравнению с ожиданием снятия блокировки перед входом в высокопроизводительную область. Таким образом, рекомендуется использовать директиву atomic всякий раз, когда вы хотите выполнить одно вычисление (x ++, x--, ++ x, --x и т. Д.) И использовать
atomic относительно эффективен, когда вам нужно включить взаимное исключение только для одной инструкции, подобное не относится к omp critical.
atomic - это отдельный оператор. Критический раздел, т.е. вы блокируете выполнение одного оператора.
критическая секция - это блокировка блока кода
Хороший компилятор переведет ваш второй код так же, как и первый.
++
и*=
) , и что , если они не поддерживаются на аппаратном уровне , они могут быть заменены наcritical
секции.