Делает ли модель памяти Intel SFENCE и LFENCE избыточными?
Модель памяти Intel гарантирует:
- Магазины не будут переупорядочены с другими магазинами
- Нагрузки не будут переупорядочены с другими грузами
http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
Я видел утверждения, что SFENCE является избыточным на x86-64 из-за модели памяти Intel, но никогда LFENCE. Делают ли приведенные выше правила модели памяти какие-либо инструкции избыточными?
1 ответ
Да, LFENCE и SFENCE бесполезны в обычном коде, потому что семантика получения / выпуска x86 для обычных магазинов делает их избыточными, если вы не используете другие специальные инструкции или типы памяти.
Единственный барьер, который имеет значение для нормального кода без блокировки, - это полный барьер (включая StoreLoad) от lock
или медленное MFENCE. предпочитать xchg
для последовательных магазинов mov
+ mfence
, Является ли загрузка и хранение единственными инструкциями, которые переупорядочиваются? потому что это быстрее.
Включает ли `xchg` в`mfence` отсутствие временных инструкций? (да, даже с инструкциями NT, пока нет памяти WC.)
Переупорядочение памяти Джеффа Прешинга, пойманное в статье " Акт", представляет собой более легкое для чтения описание того же случая, о котором рассказывает статья Бартоша, где вам нужен барьер StoreLoad, такой как MFENCE. Только MFENCE подойдет; Вы не можете построить MFENCE из SFENCE + LFENCE. ( Почему SFENCE + LFENCE (или нет?) Эквивалентно MFENCE?)
Если у вас возникли вопросы после прочтения ссылки, которую вы разместили, прочитайте другие записи в блоге Джеффа Прешинга. Они дали мне хорошее понимание предмета.:) Хотя я думаю, что я нашел лакомый кусочек о том, что SFENCE/LFENCE обычно не используются на странице Дуга Ли. Сообщения Джеффа не учитывают загрузку / хранение NT.
Связанный: Когда я должен использовать _mm_sfence _mm_lfence и _mm_mfence (мой ответ и ответ @BeeOnRope хороши. Я написал этот ответ намного дольше назад, чем этот ответ, поэтому части этого ответа показывают мою неопытность много лет назад. Мой ответ там рассматривает C++ intrinsics и C++ порядок памяти во время компиляции, который совсем не то же самое, что порядок x86 asm во время выполнения памяти. Но вы все равно не хотите _mm_lfence()
.)
SFENCE актуален только при использовании movnt
(Не временные) потоковые хранилища или работа с областями памяти с типом, установленным на что-то, отличное от обычной обратной записи. Или с clflushopt
что-то вроде слабо заказанного магазина. Магазины NT обходят кеш, а также слабо упорядочены. Обычная модель памяти x86 строго упорядочена, за исключением хранилищ NT, памяти WC (объединяющей записи) и операций строки ERMSB (см. ниже)).
LFENCE полезен только для упорядочения памяти со слабо упорядоченными нагрузками, которые очень редки. (Или возможно для заказа LoadStore с регулярными загрузками перед хранилищами NT?)
NT загружает (movntdqa
) из памяти WB все еще строго упорядочены, даже на гипотетическом будущем процессоре, который не игнорирует подсказку NT; единственный способ делать слабо упорядоченные загрузки на x86 - это чтение со слабо упорядоченной памяти (WC), и тогда я думаю только с movntdqa
, Это не происходит случайно в "нормальных" программах, поэтому вам нужно беспокоиться об этом, только если вы используете видеопамять mmap или что-то в этом роде.
(Основной вариант использования для lfence
это не упорядочение памяти вообще, это для сериализации выполнения команды, например, для смягчения Спектра, или с RDTSC. См. Сериализация LFENCE на процессорах AMD? и боковую панель "связанные вопросы" для этого вопроса.)
Упорядочение памяти в C++ и как она отображается в x86 asm
Мне стало интересно об этом пару недель назад, и я опубликовал довольно подробный ответ на недавний вопрос: атомарные операции, std:: atomic <> и порядок записи. Я включил много ссылок на материал о модели памяти C++ против аппаратных моделей памяти.
Если вы пишете на C++, используя std::atomic<>
это отличный способ сообщить компилятору, какие у вас требования к упорядочению, чтобы он не переупорядочивал ваши операции с памятью во время компиляции. Вы можете и должны использовать более слабую версию или приобретать семантику, где это уместно, вместо последовательной согласованности по умолчанию, так что компилятору вообще не нужно выдавать какие-либо барьерные инструкции на x86. Он просто должен содержать операции в исходном порядке.
В слабо упорядоченной архитектуре, такой как ARM, PPC или x86 с movnt, вам нужна инструкция барьера StoreStore между записью буфера и установкой флага, указывающего, что данные готовы. Также читателю нужна инструкция барьера LoadLoad между проверкой флага и чтением буфера.
Не считая movnt, x86 уже имеет барьеры LoadLoad между каждой загрузкой и барьеры StoreStore между каждым магазином. (Заказ на LoadStore также гарантирован). MFENCE
это все 4 вида барьеров, включая StoreLoad, который является единственным барьером, который x86 не делает по умолчанию. MFENCE следит за тем, чтобы загрузки не использовали старые предварительно выбранные значения до того, как другие потоки увидели ваши магазины и потенциально создали собственные. (Помимо того, что он является барьером для заказа магазинов NT и загрузки заказов.)
Интересный факт: x86 lock
инструкции с префиксом также являются полными барьерами памяти. Они могут использоваться в качестве замены MFENCE в старом 32-битном коде, который может работать на процессорах, не поддерживающих его. lock add [esp], 0
в противном случае не работает и выполняет цикл чтения / изменения / записи в памяти, которая, скорее всего, горячая в кэше L1 и уже находится в состоянии M протокола когерентности MESI.
SFENCE является барьером StoreStore. После хранилищ NT полезно создать семантику выпуска для следующего хранилища.
LFENCE почти всегда не имеет значения в качестве барьера памяти, потому что единственная слабо упорядоченная нагрузка
LoadLoad, а также барьер LoadStore. (loadNT / LFENCE / storeNT
препятствует тому, чтобы магазин стал глобально видимым перед загрузкой. Я думаю, что это может произойти на практике, если адрес загрузки был результатом длинной цепочки зависимостей или результатом другой загрузки, которая пропустила в кеше.)
ERMSB строковые операции
Интересный факт № 2 (спасибо @EOF
): Магазины от ERMSB (улучшено) rep movsb
/ rep stosb
на IvyBridge и позже) слабо упорядочены (но не в обход кэша). ERMSB опирается на обычные Fast-String Ops (широкие хранилища от микрокодированной реализации rep stos/movsb
это было вокруг с PPro).
Intel документирует тот факт, что хранилища ERMSB "могут казаться неработоспособными" в разделе 7.3.9.3 их Руководства для разработчиков программного обеспечения, том 1. Они также говорят
"Код, зависящий от порядка, должен записывать в дискретную переменную семафора после любых строковых операций, чтобы все процессоры могли видеть правильно упорядоченные данные"
Они не упоминают никаких барьерных инструкций, необходимых между rep movsb
и магазин в data_ready
флаг.
То, как я это читаю, есть скрытый SFENCE после rep stosb / rep movsb
(по крайней мере, забор для строковых данных, вероятно, не другие слабо заказанные магазины NT в полете). В любом случае, формулировка подразумевает, что запись во флаг / семафор становится видимой глобально после всех операций записи с перемещением строки, поэтому SFENCE/LFENCE не требуется в коде, который заполняет буфер операцией быстрой строки, а затем записывает флаг, или в коде, который читает это.
(Упорядочение LoadLoad всегда происходит, поэтому вы всегда видите данные в том порядке, в котором другие процессоры сделали их глобально видимыми. То есть использование слабо упорядоченных хранилищ для записи буфера не меняет того факта, что нагрузки в других потоках по-прежнему строго упорядочены.)
итоги: используйте обычное хранилище, чтобы написать флаг, указывающий, что буфер готов. Не нужно, чтобы читатели просто проверяли последний байт блока, написанный с помощью memset / memcpy.
Я также думаю, что магазины ERMSB не позволяют последующим магазинам передавать их, поэтому вам по-прежнему нужен только SFENCE, если вы используете movNT
, то есть rep stosb
в целом имеет семантику релиза относительно более ранние инструкции.
Есть бит MSR, который можно очистить, чтобы отключить ERMSB для новых серверов, которым нужно запускать старые двоичные файлы, которые записывают флаг "готовность данных" как часть rep stosb
или же rep movsb
или что-то. (В этом случае, я полагаю, вы получаете старый микрокод быстрой строки, который может использовать эффективный протокол кэширования, но при этом все хранилища отображаются в порядке другим ядрам).