Атомарные и неатомарные операции чтения/записи OpenMP производят одни и те же инструкции на x86_64.

Согласно спецификации OpenMP (v4.0), следующая программа содержит возможную гонку данных из-за несинхронизированного чтения/записи i:

int i{0}; // std::atomic<int> i{0};

void write() {
// #pragma omp atomic write // seq_cst
   i = 1;
}

int read() {
   int j;
// #pragma omp atomic read // seq_cst
   j = i; 
   return j;
}

int main() {
   #pragma omp parallel
   { /* code that calls both write() and read() */ }
}

Возможные решения, которые пришли мне в голову, показаны в коде в виде комментариев:

  1. для защиты записи и чтения i с #pragma omp atomic write/read,
  2. для защиты записи и чтения i с #pragma omp atomic write/read seq_cst,
  3. использовать std::atomic<int> вместо int в качестве типа i.

Вот сгенерированные компилятором инструкции для x86_64 (с -O2 во всех случаях):

GNU g++ 4.9.2:               i = 1;        j = i;
original code:               MOV           MOV
#pragma omp atomic:          MOV           MOV
// #pragma omp atomic seq_cst:  MOV           MOV
#pragma omp atomic seq_cst:  MOV+MFENCE    MOV    (see UPDATE)
std::atomic<int>:            MOV+MFENCE    MOV

clang++ 3.5.0:               i = 1;        j = i;
original code:               MOV           MOV
#pragma omp atomic:          MOV           MOV
#pragma omp atomic seq_cst:  MOV           MOV
std::atomic<int>:            XCHG          MOV

Intel icpc 16.0.1:           i = 1;        j = i;
original code:               MOV           MOV
#pragma omp atomic:          *             *
#pragma omp atomic seq_cst:  *             *
std::atomic<int>:            XCHG          MOV

* Multiple instructions with calls to __kmpc_atomic_xxx functions.

Мне интересно, почему компилятор GNU/clang не генерирует никаких специальных инструкций для #pragma omp atomic записи. Я ожидаю таких же инструкций, как и для std::atomic, то есть либо MOV+MFENCE, либо XCHG. Любое объяснение?

ОБНОВЛЕНИЕ

g++ 5.3.0 выдает MFENCE вместо #pragma omp atomic write seq_cst. Это правильное поведение, я считаю. Без seq_cst получается простое MOV, чего достаточно для атомарности, отличной от SC.

В моем Makefile была ошибка, g++ 4.9.2 также выдает MFENCE для атомарной записи CS. Извините, ребята, за это.

Clang 3.5.0 не реализует атомарность OpenMP SC, спасибо Христо Илиеву за указание на это.


person Daniel Langr    schedule 17.02.2016    source источник
comment
Мой GCC 4.9.2 генерирует mfence сразу после movl $1, i(%rip) для последовательно согласованной атомарной записи.   -  person Hristo Iliev    schedule 17.02.2016
comment
Кроме того, Clang 3.5.0 поддерживает только обычные непоследовательно непротиворечивые атомы. Он даже не имеет полной поддержки OpenMP 3.1 — см. здесь.   -  person Hristo Iliev    schedule 17.02.2016
comment
Ваш GCC 4.9.2 генерирует mfence для атомарной записи OpenMP SC? То есть с i типа int? Мой GCC только для std::atomic<int>.   -  person Daniel Langr    schedule 18.02.2016
comment
Я только что понял, что g++ 5.3.0 производит mfence для атомарной записи SC OpenMP. Итак, проблема была с (моим) g++ 4.9.2.   -  person Daniel Langr    schedule 19.02.2016
comment
Интересно, чем ваша 4.9.2 отличается от моей 4.9.2. Сомневаюсь, что спецификация машины в GCC могла быть другой. Какую ОС и дистрибутив используете?   -  person Hristo Iliev    schedule 19.02.2016
comment
@Hristo Iliev: Вы абсолютно правы, у меня была ошибка в моем Makefile, был использован неправильный источник :(. Ребята, мне очень жаль. Спасибо за ваше время и помощь.   -  person Daniel Langr    schedule 19.02.2016


Ответы (1)


Есть две возможности.

  1. Компилятор не обязан преобразовывать код C++, содержащий гонку данных, в плохой машинный код. В зависимости от модели машинной памяти обычно используемые инструкции могут уже быть атомарными и связными. Перенесите тот же код C++ на другую архитектуру, и вы увидите, что прагмы вызывают различия, которых не было в x86_64.

  2. В дополнение к потенциальному использованию различных инструкций и/или дополнительных инструкций по ограничению памяти атомарные прагмы (а также std::atomic и volatile) также ограничивают оптимизацию переупорядочения собственного кода компилятора. Они могут не относиться к вашему простому случаю, но вы, безусловно, могли заметить, что это может повлиять на удаление общих подвыражений, включая подъем вычислений вне цикла.

person Ben Voigt    schedule 17.02.2016
comment
Я согласен, однако, что одного MOV должно быть недостаточно для последовательно согласованного атомарного хранилища (см., например, здесь или лекция Херба Саттера около 00:35:00). Поэтому я бы ожидал XCHG или MFENCE вместо #pragma omp atomic write seq_cst. - person Daniel Langr; 17.02.2016
comment
Однако, если предположить, что данные правильно выровнены, mov достаточно для простого atomic (без seq_cst), так как в X86 не может произойти разрыв. (Все байты, составляющие значение, записываются атомарно с помощью mov). Без seq_cst атомарная конструкция также не подразумевает сброс OpenMP. - person Jim Cownie; 18.02.2016
comment
@Jim Cownie: Вы уверены насчет флеша? Из спецификации OpenMP. 4.0: область стирания со списком подразумевается в следующих местах: при входе и выходе из атомарной операции, выполняемой в непоследовательно согласованной атомарной области , где список содержит только место хранения, обозначенное как x в соответствии с описанием синтаксиса атомарной конструкции в Разделе 2.12.6 на стр. 127. И: Область сброса без списка подразумевается в следующих местах: ... При входе и выходе из атомарной операции, выполняемой в последовательно согласованной атомарной области. - person Daniel Langr; 18.02.2016
comment
@Daniel: см. спецификацию 4.5, в которой говорится: любая атомарная конструкция с предложением seq_cst заставляет атомарно выполняемую операцию до 17 включать неявную операцию сброса без списка. Поскольку он вызывает этот случай, я предполагаю, что это также означает, что конструкция без seq_cst не подразумевает сброса. В нем также явно (хотя и в ненормативном тексте) говорится, что непоследовательно непротиворечивая атомарная конструкция имеет ту же семантику, что и атомарная операция memory_order_relaxed в C++11/C11. (И текст, который вы цитируете, в любом случае требует только сброса рассматриваемой переменной, что делает простой mov.) - person Jim Cownie; 19.02.2016
comment
@Jim Cownie: Текст, который я цитировал, соответствует спецификации 4.5. кроме того, таким образом, даже атомарные значения, отличные от SC, подразумевают сброс, хотя и сброс со списком. И вы правы, что mov обеспечивает это. Для SC atomics я бы ожидал, что дополнительное ограждение предотвратит переупорядочивание на уровне ЦП, но в моем случае g++ не добавляет его в программу. Вот о чем я беспокоюсь; означает ли последовательная согласованность что-то другое для моделей памяти OpenMP и C++11? - person Daniel Langr; 19.02.2016
comment
@Дэниел. Я думаю, что мы в жестоком согласии. Операции чтения/записи без SC не нуждаются в каких-либо ограничениях памяти на X86. У SC есть. (И нас сбила с толку ошибка gcc :-(). - person Jim Cownie; 19.02.2016
comment
@ Джим, я тоже так думаю :). Спасибо за помощь - person Daniel Langr; 19.02.2016