Переупорядочивание грузов и магазинов на ARM

Я не эксперт по ARM, но разве эти хранилища и грузы не будут подвергаться переупорядочению хотя бы на некоторых архитектурах ARM?

  atomic<int> atomic_var; 
  int nonAtomic_var;
  int nonAtomic_var2;

  void foo()
  {       
          atomic_var.store(111, memory_order_relaxed);
          atomic_var.store(222, memory_order_relaxed);
  }

  void bar()
  {       
          nonAtomic_var = atomic_var.load(memory_order_relaxed);
          nonAtomic_var2 = atomic_var.load(memory_order_relaxed);
  }

Мне не удалось заставить компилятор поставить между ними барьеры памяти.

Я пробовал что-то вроде ниже (на x64):

$ arm-linux-gnueabi-g++ -mcpu=cortex-a9 -std=c++11 -S -O1 test.cpp

И у меня есть:

_Z3foov:
          .fnstart
  .LFB331:
          @ args = 0, pretend = 0, frame = 0
          @ frame_needed = 0, uses_anonymous_args = 0
          @ link register save eliminated.
          movw    r3, #:lower16:.LANCHOR0
          movt    r3, #:upper16:.LANCHOR0
          mov     r2, #111
          str     r2, [r3]
          mov     r2, #222
          str     r2, [r3]
          bx      lr
          ;...
  _Z3barv:
          .fnstart
  .LFB332:
          @ args = 0, pretend = 0, frame = 0
          @ frame_needed = 0, uses_anonymous_args = 0
          @ link register save eliminated.
          movw    r3, #:lower16:.LANCHOR0
          movt    r3, #:upper16:.LANCHOR0
          ldr     r2, [r3]
          str     r2, [r3, #4]
          ldr     r2, [r3]
          str     r2, [r3, #8]
          bx      lr

Никогда не переупорядочивает грузы и магазины в одном месте на ARM? Я не нашел такого ограничения в документации по ARM.

Я спрашиваю о стандарте C++11, который гласит:

Все модификации любой конкретной атомарной переменной происходят в общем порядке, специфичном для этой атомарной переменной.

1 ответ

Общий порядок для одной переменной существует из-за когерентности кеша (MESI): хранилище не может выполнить фиксацию из буфера хранилища в кэш L1d и стать глобально видимым для других потоков, если ядро ​​не владеет монопольным доступом к этой строке кеша. (Состояние MESI Exclusive или Modified.)

Эта гарантия C++ не требует каких-либо препятствий для реализации на любой нормальной архитектуре ЦП, потому что все обычные ISA имеют согласованные кеши, обычно использующие вариант MESI. Вот почемуvolatile работает как устаревшая / UB версия mo_relaxed atomicв основных реализациях C++ (но обычно не делайте этого). См. Также Когда использовать volatile с многопоточностью? Больше подробностей.

(Некоторые системы существуют с двумя разными типами ЦП, которые совместно используют память, например, микроконтроллер + DSP, но C++ std::threadне будет запускать потоки в ядрах, которые не имеют согласованного представления об этой памяти. Таким образом, компиляторы должны создавать код только для ядер ARM в одном и том же внутреннем общем домене когерентности.)


Для любого заданного атомарного объекта всегда будет существовать общий порядок модификации всеми потоками (что гарантируется указанным вами стандартом ISO C++), но вы не знаете заранее, что он будет, если вы не установите синхронизацию между потоками.

например, при разных запусках этой программы сначала могут идти обе загрузки или одна загрузка, затем обе загрузки, а затем другая загрузка.

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

memory_order_relaxedтолько атомарная операция с этой переменной, а не упорядочение по отношению к ней. что-нибудь еще. Единственный порядок, который фиксируется во время компиляции, - это wrt. другие обращения этого потока к той же атомарной переменной.

Различные потоки согласятся с порядком изменения этой переменной, но могут не согласиться с порядком глобального изменения для всех объектов. (ARMv8 сделал модель памяти ARM multi-copy-atomic, поэтому это невозможно (и, вероятно, ни одна из предыдущих ARM не нарушала это), но POWER в реальной жизни позволяет двум независимым потокам чтения не согласовывать порядок хранения двумя другими независимыми писателями. потоки. Это называется переупорядочением IRIW. Будут ли две атомарные записи в разные места в разных потоках всегда отображаться в одном и том же порядке другими потоками?)

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

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

Никогда не переупорядочивает грузы и магазины в одном и том же месте на ARM?

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


Некоторые гарантии упорядочения в стандарте C++ даже называются "когерентность записи-записи" и другие виды согласованности. ISO C++ явно не требует согласованных кешей (возможна реализация на ISA, которая требует явной очистки), но не будет эффективной.

http://eel.is/c++draft/intro.races

[Примечание: четыре предшествующих требования согласованности эффективно запрещают компилятор переупорядочивать атомарные операции для одного объекта, даже если обе операции являются ослабленными нагрузками. Это фактически обеспечивает гарантию согласованности кеша, предоставляемую большинством оборудования, доступного для атомарных операций C++.- конец примечания]


Большая часть вышесказанного касается порядка модификации, а не переупорядочения LoadLoad.

Это отдельная вещь. C++ гарантирует согласованность чтения-чтения, т. Е. 2 ​​чтения одного и того же атомарного объекта одним и тем же потоком происходят в программном порядке относительно друг друга.

http://eel.is/c++draft/intro.races

Если вычисление значения A атомарного объекта M происходит до вычисления значения B для M, и A принимает свое значение из побочного эффекта X на M, тогда значение, вычисленное B, должно быть либо значением, сохраненным X, либо сохраненным значением от побочного эффекта Y на М, где Y следует X в порядке модификации M. [Примечание: это требование известно как согласованность чтения-чтения. - конец примечания]

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

Это одно из 4 условий, о которых я говорил в предыдущей цитате.

Тот факт, что компиляторы компилируют его в две простые загрузки ARM, является достаточным доказательством того, что ARM ISA также гарантирует это. (Потому что мы точно знаем, что этого требует ISO C++.)

Я не знаком с руководствами по ARM, но, по-видимому, он где-то там.

См. Также Учебное введение в модели ослабленной памяти ARM и POWER - документ, в котором подробно рассказывается о том, какие переупорядочения разрешены / запрещены для различных тестовых случаев.

Другие вопросы по тегам