C11 Atomic Acquire / Release и x86_64 отсутствие согласованности загрузки / сохранения?

Я борюсь с разделом 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):

  • все (не атомарные) чтения и записи «упорядочиваются до» и, следовательно, «происходят до» атомарной записи / выпуска в потоке «1»,
  • который «происходит между потоками до» атомарного чтения / получения в потоке «2» (когда он читает «истина»),
  • что, в свою очередь, «упорядочено до» и, следовательно, «происходит до» (не атомарных) операций чтения и записи (в потоке «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 и даст ожидаемый результат.

На x86 все обычные (не временные) хранилища имеют семантику выпуска. Руководство разработчика программного обеспечения для архитектур Intel® 64 и IA-32, том 3 (3A, 3B, 3C и 3D): Руководство по системному программированию, 8.2.3.3 Stores Are Not Reordered With Earlier Loads. Итак, ваш компилятор правильно переводит ваш код (как это удивительно), так что ваш код фактически является полностью последовательным, и одновременно ничего интересного не происходит.   —  person Chris Hall    schedule 09.02.2020

См. также:  понимание структур, указателей и определений типов

Спасибо ! (Я тихонько сходил с ума.) FWIW Я рекомендую ссылку — в частности, раздел 3, Модель программиста. Но чтобы избежать ошибки, в которую я попал, обратите внимание, что в 3.1 Абстрактная машина есть аппаратные потоки, каждый из которых является одним упорядоченным потоком выполнения инструкций (выделено мной). Теперь я могу вернуться к попыткам понять стандарт C11 … с меньшим когнитивным диссонансом :-)   —  person Chris Hall    schedule 10.02.2020

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 1
  1. Chris Hall

    Модель памяти x86 — это в основном последовательная согласованность плюс буфер хранилища (с пересылкой хранилища). Итак, каждый магазин — это релиз-магазин 1. Вот почему только хранилища seq-cst нуждаются в особых инструкциях. (Отображение атомики C / C ++ 11 в asm ). Кроме того, в https://stackoverflow.com/tags/x86/info есть ссылки на документы 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, 3C и 3D): Руководство по системному программированию, 8.2.3.3 Магазины не переупорядочиваются с более ранними загрузками.


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

    Я нахожу x86-TSO: строгая и удобная модель программиста для x86 Мультипроцессоры более читабельны, чем (связанные) Formal Описание, на которое вы ссылались. Но моя настоящая цель — полностью понять разделы 5.1.2.4 и 7.17.3 стандарта C11 / C18. В частности, я думаю, что получаю Release / Acquire / Acquire + Release, но memory_order_seq_cst определяется отдельно, и я изо всех сил пытаюсь понять, как все они сочетаются друг с другом :-( person Chris Hall; 11.02.2020

    @ChrisHall: Я обнаружил, что это помогло понять, насколько слабым может быть acq / rel, и для этого вам нужно посмотреть на такие машины, как POWER, которые могут переупорядочивать IRIW. (который запрещает seq-cst, но не acq / rel). Будут ли две атомарные записи в разные места в разных потоках всегда отображаться в одном порядке другими потоками?. Также в Как достичь барьера StoreLoad в C ++ 11? обсуждается, насколько мало стандарт формально гарантирует упорядочивание вне синхронизов. с или все-seq-cst случаями. person Chris Hall; 11.02.2020

    @ChrisHall: Главное, что делает seq-cst, — это блокировать переупорядочивание StoreLoad. (На x86 это единственное, что он делает помимо acq / rel). preshing.com/20120515/memory-reordering-caught-in-the -act использует asm, но это эквивалентно seq-cst vs. acq / rel person Chris Hall; 11.02.2020

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: