В чем (небольшая) разница в расслабляющих атомных правилах?
После того, как Херб Саттерс отлично рассказал о "атомном оружии", я немного запутался в примерах " Расслабленной атомики".
Я взял с собой, что атом в модели памяти C++ (SC-DRF = последовательная согласованность для Data Race Free) "получает" при загрузке / чтении.
Я понимаю, что для загрузки [и магазина] по умолчанию std::memory_order_seq_cst
и, следовательно, два одинаковы:
myatomic.load(); // (1)
myatomic.load(std::memory_order_seq_cst); // (2)
Пока все хорошо, не задействовано Relaxed Atomics (и, услышав доклад, я никогда не буду использовать relaxed. Всегда. Обещание. Но когда кто-то спросит меня, мне, возможно, придется объяснить...).
Но почему это "расслабленная" семантика, когда я использую
myatomic.load(std::memory_order_acquire); // (3)
Так как нагрузка приобретает, а не освобождает, почему это отличается от (1)
а также (2)
? Что на самом деле здесь расслаблено?
Единственное, о чем я могу думать, это то, что я неправильно понял, что нагрузка означает приобретение. И если это правда, и по умолчанию seq_cst
означает и то, и другое, не означает ли это полный забор - ничто не может пройти ни по этой инструкции, ни вниз? Я должен был неправильно понять эту часть.
[и симметрично для хранения и выпуска ].
1 ответ
Это может быть немного запутанным, чтобы позвонить myatomic.load(std::memory_order_acquire);
"расслабленная атомная" нагрузка, так как существует std::memory_order_relaxed
Вы правильно заметили, что последовательная согласованная нагрузка является загрузочной нагрузкой, но у нее есть дополнительное требование: последовательная согласованная нагрузка также является частью общего глобального порядка для всех операций seq_cst.
Он вступает в игру, когда вы имеете дело с более чем одной атомарной переменной: отдельные порядки модификации двух атомик могут появляться в разном относительном порядке по отношению к разным потокам, если только не установлена последовательная согласованность.
Если вы "ослабите" некоторые требования к порядку следования seq_cst, вы увидите mo_acq_rel
(и чистое приобретение, и чистое высвобождение).
Даже более расслабленно, чем это mo_relaxed
; без заказа по. что-нибудь еще, просто атомарность1.
При компиляции для большинства ISA загрузка seq_cst может использовать тот же asm, что и загрузка загрузки; мы предпочитаем делать магазины дорогими, а не загруженными. Сопоставления C/C++11 с процессорами для ISA, включая x86, POWER, ARMv7, ARMv8 включают 2 альтернативы для некоторых ISA. Чтобы быть совместимыми друг с другом, компиляторы для одной и той же платформы должны выбирать одну и ту же стратегию, в противном случае хранилище seq_cst в одной функции может быть переупорядочено с загрузкой seq_cst в другой функции.
На типичном ЦП, где модель памяти включает буфер хранилища и согласованный кеш, если вы сохраняете и затем перезагружаете в одном потоке, seq_cst требует, чтобы вы не позволяли перезагрузке происходить до тех пор, пока хранилище не станет глобально видимым для всех потоков. Это означает либо полный барьер (включая StoreLoad) после сохранения seq_cst или до загрузки seq_cst. Поскольку дешевые грузы более ценны, чем дешевые магазины, обычное отображение выбирает x86mov
+ mfence
для магазинов, например. (То же самое относится к загрузке любого другого местоположения; не может сделать это, пока магазин не зафиксирует. Но переадресация хранилища, позволяющая вам видеть свои собственные магазины до того, как они станут глобально видимыми, - это то, что происходит в книге Джеффа Прешинга переупорядочение памяти, пойманной в действии)
Это практический пример создания глобального общего порядка операций с различными переменными, с которым могут согласиться все потоки. (x86 asm предоставляет acq_rel для чистой загрузки / чистого хранилища или seq_cst дляlock
-приставленные атомарные инструкции RMW. Таким образом, пример Preshing x86 asm точно соответствует C++11mo_release
магазины вместо mo_seq_cst
.
ARMv8 / AArch64 интересен: у него есть STLR (хранилище с последовательным выпуском) и LDAR (загрузка загрузки). Вместо остановки всех последующих загрузок до тех пор, пока буфер хранилища не истощится и не зафиксирует STLR в кеш L1d (глобальная видимость), реализация может быть более эффективной.
Ожидание сброса должно произойти только до выполнения LDAR; другие загрузки могут выполняться, и даже более поздние хранилища могут фиксироваться в L1d. (Последовательный выпуск по-прежнему является как минимум односторонним препятствием). Чтобы быть таким эффективным / слабым, LDAR должен исследовать буфер хранилища, чтобы проверить хранилища STLR. Но если ты сможешь это сделать,mo_seq_cst
магазины могут быть значительно дешевле, чем на x86, если вы сразу после этого не выполните загрузку seq_cst чего-либо еще.
На большинстве других ISA единственный вариант восстановления последовательной согласованности - это полная барьерная инструкция (после сохранения). Это блокирует все последующие загрузки и сохранения до тех пор, пока все предыдущие хранилища не зафиксируются в кэше L1d. Но это не то, что ISO C++seq_cst
подразумевает или требует, просто только AArch64 может быть настолько сильным, насколько требует ISO C++, но не сильнее.
(Компиляция для многих других слабо упорядоченных ISA должна продвигать acq / release к значительно более сильному, чем необходимо, например, ARMv7 нужен полный барьер для хранилищ релизов.)
Сноска 1: (Например, то, что вы получили в старом коде до C++11, используя атомики для самостоятельной прокрутки, используя volatile
без преград).
И если это правда, и по умолчанию
seq_cst
означает оба, разве это не означает полный забор
Это абсолютно не означает и то, и другое или "полный забор", что бы это ни значило.
seq_cst
подразумевает
- приобретать только при загрузке
- и выпускать только при работе магазина.
Таким образом, это подразумевает и то, и другое только для операций, которые объединяют оба: атомарные операции RMW.
Последовательная согласованность также означает, что эти операции упорядочены глобально, то есть: все операции отмечены seq_cst
всей программы выполняются в некотором последовательном порядке, то есть порядке, совместимом с последовательностью операций в каждом потоке. В нем ничего не говорится о порядке других атомарных операций по отношению к этим "последовательным" операциям.
Намерение seq_cst
операция над атомарным объектом не является "забором", который сделает все другие операции с памятью последовательными.