В чем разница между atomic и critical в OpenMP?

в чем разница между атомарным и критическим в OpenMP?

Я могу это сделать

#pragma omp atomic
g_qCount++;

но разве это не то же самое, что

#pragma omp critical
g_qCount++;

?

8 ответов


эффект на g_qCount тот же, но то, что сделано, отличается.

критический раздел OpenMP является полностью общим - он может окружать любой произвольный блок кода. Однако вы платите за эту общность, накладывая значительные накладные расходы каждый раз, когда поток входит и выходит из критического раздела (поверх присущей стоимости сериализации).

(кроме того, в OpenMP все неназванные критические разделы считаются идентичными (если вы предпочитаете, есть только одна блокировка для всех неназванных критических разделов), так что если один поток находится в одном [неназванном] критическом разделе, Как указано выше, ни один поток не может войти в любой [неназванный] критический раздел. Как вы можете догадаться,вы можете обойти это, используя именованные критические разделы).

атомарная операция имеет гораздо более низкие накладные расходы. Там, где это возможно, он использует аппаратное обеспечение, обеспечивающее (скажем) операцию атомарного приращения; в этом случае нет необходимости в блокировке / разблокировке при входе / выходе из строки кода, это просто делает атомную приращение которой в нем нельзя было помешать.

плюсы в том, что накладные расходы намного ниже, и один поток, находящийся в атомной операции, не блокирует никаких (разных) атомарных операций. Недостатком является ограниченный набор операций, который поддерживает atomic.

конечно, в любом случае вы несете расходы на сериализацию.


в OpenMP все неназванные критические разделы являются взаимоисключающими.

самое важное различие между critical и atomic заключается в том, что atomic может защитить только одно назначение, и вы можете использовать его с конкретными операторами.


критическая секция:

  • обеспечивает сериализацию блоков кода.
  • может быть расширен для сериализации групп блоков с правильным использованием тега "name".

  • медленнее!

атомарная операция:

  • намного быстрее!

  • только обеспечивает сериализацию определенной операции.


самый быстрый способ не является ни критичным, ни атомной. Примерно, сложение с критическим сечением в 200 раз дороже простого сложения, атомное сложение в 25 раз дороже простого сложения.

самый быстрый вариант (не всегда применимый) дать каждому потоку свой собственный счетчик и сделать уменьшить операцию, когда вам нужна общая сумма.


ограничения atomic важны. Они должны быть подробно описаны на спецификации OpenMP. MSDN предлагает быстрый Шпаргалка, как я не удивлюсь, если это не изменится. (Visual Studio 2012 имеет реализацию OpenMP с марта 2002 года.) Процитировать MSDN:

оператор выражения должен иметь одну из следующих форм:

xбинарный оператор=expr

x++

++x

x--

--x

в предыдущих выражений: x это lvalue выражение скалярного типа. expr - это выражение со скалярным типом, и оно не ссылается на объект, обозначенный x. бинарный оператор не является перегруженным оператором и является одним из +, *, -, /, &, ^, |, <<, или >>.

я рекомендую использовать atomic когда вы можете и имени критические разделы в противном случае. Называть их важно; таким образом вы избежите отладочных головных болей.


уже большие объяснения здесь. Однако, мы можем нырнуть немного глубже. Чтобы понять основную разницу между атомные и критическая секция понятия в OpenMP, мы должны понять концепцию замок первый. Давайте рассмотрим, почему нам нужно использовать замки.

параллельная программа выполняется несколькими потоками. Детерминированные результаты будут происходить тогда и только тогда, когда мы выполняем синхронизация между этими потоками. Конечно,синхронизация между потоками не всегда требуется. Мы имеем в виду те случаи, которые синхронизация необходимо.

для того чтобы синхронизация потоки в многопоточной программе, мы будем использовать замок. Когда доступ должен быть ограничен только одним потоком одновременно, замок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, Альфа, ARM или сила.

try:    LW R1, lock
        BNEZ R1, try
        ADDI R1, R1, #1
        SW R1, lock

эта программа, кажется, в порядке, но это не так. Приведенный выше код страдает от предыдущей проблемы;синхронизация. Давайте найдем проблему. Предположим, что начальное значение lock равно нулю. Если два потока запускают этот код, один может достичь SW R1, блокировка до другой читает замок переменной. Таким образом, оба они думают, что замок бесплатно. Чтобы решить этот вопрос, есть еще один обучение, а не просто LW и SW. Она называется Чтение-Изменение-Запись инструкция. Это сложная инструкция (состоящая из subinstructions), который обеспечивает приобретение замка процедура выполняется только один потоком. Разница Чтение-Изменение-Запись по сравнению с простым читать и написать инструкции заключается в том, что он использует другой способ загрузка и хранение. Он использует LL(Load Linked) для загрузки переменной блокировки и SC(хранить условно) для записи в переменную lock. Дополнительный Связь Регистров использовано для того чтобы убедить процедуры приема замка сделано одиночным потоком. Алгоритм приведен ниже.

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 (новую переменную), в то время как мы можем использовать фактическую переменную (которую мы выполняем над ней), как переменную lock?

С помощью новая переменной для замки приведет к критическая секция при использовании фактический переменная как блокировка приведет к атомные. Критический раздел полезен, когда мы выполняем много вычислений (более одной строки) по фактической переменной. Это потому, что, если результат этих вычислений не будет записан на фактическую переменную, вся процедура должна быть повторена для вычисления результатов. Это может привести к плохой производительности по сравнению с ожиданием разблокировки блокировки перед входом в вычислительную область. Таким образом, рекомендуется использовать атомные директива всякий раз, когда вы хотите выполнить одно вычисление (x++, x--, ++x, --x и т. д.) и использовать критическое директива, когда более вычислительно сложная область выполняется интенсивной секцией.


atomic относительно эффективен, когда вам нужно включить взаимное исключение только для одной инструкции, аналогичной, не верно о критическом omp.


atomic-это критический раздел одного оператора, т. е. вы блокируете выполнение одного оператора

критический раздел-это блокировка блока кода

хороший компилятор переведет ваш второй код так же, как и первый