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


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

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

#pragma omp atomic
g_qCount++;

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

#pragma omp critical
g_qCount++;

?

8 89

8 ответов:

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

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

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

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

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

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

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

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

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

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

  • медленнее!

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

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

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

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

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

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

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

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

x++

++x

x--

--x

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

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

уже большие объяснения здесь. Однако, мы можем нырнуть немного глубже. Чтобы понять основную разницу между 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

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

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 исходит из того, что:

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

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

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

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

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

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