C11 Atomi c Acquire / Release и x86_64 отсутствие согласованности загрузки / хранения? - PullRequest
10 голосов
/ 09 февраля 2020

Я борюсь с разделом 5.1.2.4 Стандарта C11, в частности с семантикой Release / Acquire. Я отмечаю, что https://preshing.com/20120913/acquire-and-release-semantics/ (среди прочих) утверждает, что:

... Семантика выпуска предотвращает переупорядочение памяти в выпуске записи с любой предшествующей ему операцией чтения или записи. в программном порядке.

Итак, для следующего:

typedef struct test_struct
{
  _Atomic(bool) ready ;
  int  v1 ;
  int  v2 ;
} test_struct_t ;

extern void
test_init(test_struct_t* ts, int v1, int v2)
{
  ts->v1 = v1 ;
  ts->v2 = v2 ;
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}

extern int
test_thread_1(test_struct_t* ts, int v2)
{
  int v1 ;
  while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v2 = v2 ;       // expect read to happen before store/release 
  v1     = ts->v1 ;   // expect write to happen before store/release 
  atomic_store_explicit(&ts->ready, true, memory_order_release) ;
  return v1 ;
}

extern int
test_thread_2(test_struct_t* ts, int v1)
{
  int v2 ;
  while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v1 = v1 ;
  v2     = ts->v2 ;   // expect write to happen after store/release in thread "1"
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
  return v2 ;
}

, где они выполняются:

>   in the "main" thread:  test_struct_t ts ;
>                          test_init(&ts, 1, 2) ;
>                          start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
>                          start thread "1" which does: r1 = test_thread_1(&ts, 4) ;

Поэтому я ожидал бы поток " 1 "будет иметь r1 == 1, а нить" 2 "будет иметь r2 = 4.

Я бы ожидал этого, потому что (в соответствии с пунктами 16 и 18 раздела 5.1.2.4):

  • все операции чтения (и не записи в атомах c) "упорядочены до" и, следовательно, "выполняются до" записи / выпуска атома c в потоке "1",
  • , которые "между потоками" -happens-before "атоми c читают / получают в потоке" 2 "(когда он читает" true "),
  • , который, в свою очередь," секвенируется до "и, следовательно," происходит до "(не atomi c) читает и пишет (в потоке "2").

Однако вполне возможно, что я не смог понять стандарт.

Я заметил, что код, сгенерированный для x86_64, включает:

test_thread_1:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  jne    <test_thread_1>  -- while is true
  mov    %esi,0x8(%rdi)   -- (W1) ts->v2 = v2
  mov    0x4(%rdi),%eax   -- (R1) v1     = ts->v1
  movb   $0x1,(%rdi)      -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
  retq   

test_thread_2:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  je     <test_thread_2>  -- while is false
  mov    %esi,0x4(%rdi)   -- (W2) ts->v1 = v1
  mov    0x8(%rdi),%eax   -- (R2) v2     = ts->v2   
  movb   $0x0,(%rdi)      -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
  retq   

И при условии , что R1 и X1 происходят в таком порядке, это дает ожидаемый результат.

Но я понимаю x86_64 так, что чтение происходит по порядку с другими операциями чтения, а запись происходит по порядку с другими операциями записи, но чтение и запись могут происходить не по порядку друг с другом. Что подразумевает, что X1 может произойти раньше, чем R1, и даже X1, X2, W2, R1 произойдут в таком порядке - я полагаю. [Это кажется невероятно маловероятным, но если R1 были задержаны некоторыми проблемами с кешем?]

Пожалуйста: что я не понимаю?

Я отмечаю, что если я изменю загрузки / хранилища ts->ready до memory_order_seq_cst, код, сгенерированный для магазинов:

  xchg   %cl,(%rdi)

, что соответствует моему пониманию x86_64 и даст ожидаемый результат.

1 Ответ

1 голос
/ 11 февраля 2020

Модель памяти x86 в основном последовательная согласованность плюс буфер хранилища (с пересылкой хранилища). Так что каждый магазин - это релиз-магазин 1 . Вот почему только seq-cst магазины нуждаются в специальных инструкциях. ( C / C ++ 11 атомных отображений в asm ). Кроме того, { ссылка } имеет некоторые ссылки на документы x86, включая формальное описание модели памяти x86-TSO (в основном нечитаемо для большинства людей; требует просмотра множества определений) .

Поскольку вы уже читаете превосходную серию статей Джеффа Прешинга, я укажу вам еще одну, более детальную: https://preshing.com/20120930/weak-vs-strong-memory-models/

Единственное изменение порядка, которое разрешено на x86, - это StoreLoad, а не LoadStore , если говорить в этих терминах. (Переадресация магазина может сделать дополнительную забаву, если загрузка только частично перекрывает хранилище; Глобально невидимые инструкции по загрузке , хотя вы никогда не получите это в сгенерированном компилятором коде для stdatomic.)

@ EOF прокомментировал правильную цитату из руководства Intel:

Руководство разработчика программного обеспечения для архитектуры Intel® 64 и IA-32 Том 3 (3A, 3B, 3 C & 3D): Системное программирование Руководство, 8.2.3.3. Хранилища не переупорядочиваются с более ранними загрузками.


Сноска 1: игнорирование слабо упорядоченных хранилищ NT; вот почему вы обычно sfence после выполнения NT магазинов. Реализации C11 / C ++ 11 предполагают, что вы не используете хранилища NT. Если да, используйте _mm_sfence перед операцией выпуска, чтобы убедиться, что она соответствует вашим хранилищам NT. (Как правило, не используйте _mm_mfence / _mm_sfence, в других случаях ; обычно вам нужно только заблокировать переупорядочение во время компиляции. Или, конечно, просто использовать stdatomi c.)

...