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

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

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

#pragma omp atomic
g_qCount++;

но это не то же самое, что

#pragma omp critical
g_qCount++;

?


person codereviewanskquestions    schedule 17.10.2011    source источник


Ответы (7)


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

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

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

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

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

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

person Jonathan Dursi    schedule 17.10.2011
comment
вы можете потерять портативность - я не уверен, что это правда. Стандарт (версия 2.0) определяет, какие атомарные операции разрешены (в основном, такие как ++ и *=) и что, если они не поддерживаются аппаратно, их можно заменить critical разделами. - person Dan R; 27.06.2016
comment
@DanRoche: Да, вы совершенно правы. Не думаю, что это утверждение когда-либо было правильным, я исправлю его сейчас. - person Jonathan Dursi; 27.06.2016
comment
Несколько дней назад я следил за учебником по OpenMP, и, насколько я понял, есть разница в двух разных кодах. То есть результат может отличаться, поскольку критическая секция гарантирует, что инструкция выполняется потоком раз, однако возможно, что инструкция: g_qCount = g_qCount + 1; для потока 1 результат g_qCount просто сохраняется только в буфере записи, а не в памяти RAM, а когда поток 2 извлекает значение g_qCount, он просто считывает его в RAM, а не в буфере записи. Атомарная инструкция гарантирует, что инструкция сбрасывает данные в память. - person Giox79; 12.03.2018

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

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

person Michael    schedule 27.02.2012
comment
Это лучше было бы комментарием (или редактированием) предыдущего ответа. - person kynan; 10.03.2015

Критический раздел:

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

  • помедленнее!

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

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

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

person efarsarakis    schedule 23.12.2013
comment
Но этот ответ очень удобочитаем и будет отличным обобщением первого ответа. - person Michał Miszczyszyn; 15.12.2015

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

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

person Andrii    schedule 26.01.2014
comment
Я не согласен со всеми цифрами, которые вы упомянули в своем объяснении. Предполагая, что x86_64, атомарная операция будет иметь несколько накладных расходов цикла (синхронизация строки кэша) по стоимости примерно одного цикла. Если бы в противном случае у вас была бы стоимость «истинного совместного использования», накладные расходы равны нулю. Критическая секция влечет за собой стоимость блокировки. В зависимости от того, взята ли уже блокировка или нет, накладные расходы составляют примерно 2 атомарных инструкции ИЛИ два запуска планировщика и время ожидания - это обычно значительно больше, чем в 200 раз. - person Klaas van Gend; 01.04.2016
comment
Предлагаемый вами вариант может привести к огромной потребности в памяти, которой у нас может не быть. Например, если я работаю с данными размером 1000x1000x1000 ячеек и что я работаю с 10 или 100 потоками, внутренние копии, созданные для каждого потока, обязательно заполнят ОЗУ. - person Noureddine; 01.01.2021

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

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

x binop = expr

x++

++x

x--

--x

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

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

person darda    schedule 01.11.2013
comment
Это еще не все, у нас есть другие расширенные атомарные директивы, такие как: #pragma omp aromic update (или чтение, обновление, запись, захват), поэтому это позволяет нам иметь другое полезное утверждение - person pooria; 14.07.2016

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

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

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

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 до того, как другой прочитает переменную lock. Таким образом, они оба думают, что блокировка свободна. Для решения этой проблемы предлагается другая инструкция, а не простые LW и SW. Это называется инструкцией чтение-изменение-запись. Это сложная инструкция (состоящая из подинструкций), которая гарантирует, что процедура получения блокировки выполняется только одним потоком за раз. Отличие команд Чтение-Изменение-Запись от простых инструкций Чтение и Запись состоит в том, что они используют другой способ Загрузка < / em> и Хранение. Он использует 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

Когда регистр ссылки сбрасывается, если другой поток предположил, что блокировка свободна, он не сможет снова записать увеличенное значение в блокировку. Таким образом достигается параллелизм доступа к переменной lock.

Основное различие между критическим и атомарным заключается в том, что:

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

Использование новой переменной для блокировок приведет к критическому разделу, а использование фактической переменной в качестве блокировки приведет к к концепции атомарной. Критический раздел полезен, когда мы выполняем много вычислений (более одной строки) над фактической переменной. Это потому, что, если результат этих вычислений не может быть записан в фактическую переменную, всю процедуру следует повторить для вычисления результатов. Это может привести к снижению производительности по сравнению с ожиданием снятия блокировки перед входом в высокопроизводительную вычислительную область. Таким образом, рекомендуется использовать директиву atomic всякий раз, когда вы хотите выполнить одно вычисление (x ++, x--, ++ x, --x и т. Д.) И использовать critical < / em>, когда более сложная в вычислительном отношении область выполняется интенсивным разделом.

person hexpheus    schedule 02.05.2018

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

критическая секция - это блокировка блока кода

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

person Wissam Y. Khalil    schedule 18.12.2013
comment
Это просто неправильно. Пожалуйста, не говорите о том, чего вы не понимаете. - person jcsahnwaldt Reinstate Monica; 30.10.2018