Имеет ли смысл использовать инструкцию LFENCE на процессорах x86/x86_64?
Часто в интернете нахожу, что LFENCE
не имеет смысла в процессорах x86, т.е. ничего не делает, поэтому вместо MFENCE
мы можем абсолютно безболезненно использовать SFENCE
, так как MFENCE
знак равно SFENCE
+ LFENCE
знак равно SFENCE
+ NOP
знак равно SFENCE
,
Но если LFENCE
не имеет смысла, тогда почему у нас есть четыре подхода для создания последовательной согласованности в x86 / x86_64:
LOAD
(без забора) иSTORE
+MFENCE
LOAD
(без забора) иLOCK XCHG
MFENCE
+LOAD
а такжеSTORE
(без забора)LOCK XADD
(0) иSTORE
(без забора)
Взято отсюда: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
А также выступления Херба Саттера на странице 34 внизу: https://skydrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&wdo=2&authkey=!AMtj_EflYn2507c
Если LFENCE
ничего не делать, тогда подход (3) будет иметь следующие значения: SFENCE + LOAD and STORE (without fence)
но нет смысла делать SFENCE
до LOAD
, Т.е. если LFENCE
ничего не делает, подход (3) не имеет смысла.
Имеет ли смысл инструкция LFENCE
в процессорах x86/x86_64?
ОТВЕТ:
1. LFENCE
требуется в случаях, описанных в принятом ответе, ниже.
2. Подход (3) следует рассматривать не независимо, а в сочетании с предыдущими командами. Например, подход (3):
MFENCE
MOV reg, [addr1] // LOAD-1
MOV [addr2], reg //STORE-1
MFENCE
MOV reg, [addr1] // LOAD-2
MOV [addr2], reg //STORE-2
Мы можем переписать код подхода (3) следующим образом:
SFENCE
MOV reg, [addr1] // LOAD-1
MOV [addr2], reg //STORE-1
SFENCE
MOV reg, [addr1] // LOAD-2
MOV [addr2], reg //STORE-2
И здесь SFENCE
имеет смысл предотвратить переупорядочение STORE-1 и LOAD-2. Для этого после команды STORE-1 SFENCE
сбрасывает Store-Buffer.
3 ответа
Итог (TL;DR): LFENCE
само по себе кажется бесполезным для упорядочения памяти, однако это не делает SFENCE
замена для MFENCE
, "Арифметическая" логика в вопросе не применима.
Вот выдержка из Руководства Intel для разработчиков программного обеспечения, том 3, раздел 8.2.2 (издание 325384-052US от сентября 2014 г.), который я использовал в другом ответе
- Чтения не переупорядочиваются с другими чтениями.
- Записи не переупорядочиваются со старыми чтениями.
- Записи в память не переупорядочиваются с другими записями, за следующими исключениями:
- запись выполняется с помощью инструкции CLFLUSH;
- потоковые хранилища (записи), выполняемые с помощью временных инструкций перемещения (MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS и MOVNTPD); а также
- строковые операции (см. раздел 8.2.4.1).
- Чтения могут быть переупорядочены со старыми записями в разных местах, но не со старыми записями в том же месте.
- Чтение или запись не могут быть переупорядочены с помощью инструкций ввода / вывода, заблокированных инструкций или инструкций сериализации.
- Чтение не может пройти более ранние инструкции LFENCE и MFENCE.
- Пишет не может пройти более ранние инструкции LFENCE, SFENCE и MFENCE.
- Инструкции LFENCE не могут пройти ранее прочитанные.
- Инструкции SFENCE не могут проходить ранее записи.
- Инструкции MFENCE не могут передавать ранее прочитанные или записанные данные.
Отсюда следует, что:
MFENCE
является полным забором памяти для всех операций со всеми типами памяти, независимо от того, является ли он временным или нет.SFENCE
только предотвращает переупорядочение записей (в другой терминологии это барьер StoreStore) и полезен только вместе с временными хранилищами и другими инструкциями, перечисленными в качестве исключений.LFENCE
предотвращает изменение порядка чтения с последующим чтением и записью (т. е. объединяет барьеры LoadLoad и LoadStore). Однако первые две марки говорят, что барьеры LoadLoad и LoadStore всегда на месте, без исключений. СледовательноLFENCE
одно бесполезно для упорядочивания памяти.
Чтобы поддержать последнее требование, я посмотрел все места, где LFENCE
упоминается во всех 3 томах руководства Intel, и не нашел ни одного, который сказал бы, что LFENCE
требуется для согласованности памяти. Четное MOVNTDQA
- единственная пока не временная инструкция по загрузке - упоминает MFENCE
но нет LFENCE
,
Обновление: см. Ответы на вопрос: почему (или нет?) SFENCE + LFENCE эквивалентны MFENCE? для правильных ответов на догадки ниже
Будь то MFENCE
эквивалентно "сумме" двух других заборов или нет, это сложный вопрос. На первый взгляд, только из трех инструкций по забору MFENCE
обеспечивает барьер StoreLoad, то есть предотвращает изменение порядка чтения с более ранними записями. Однако правильный ответ требует знать больше, чем приведенные выше правила; а именно, важно, чтобы все инструкции по забору были упорядочены относительно друг друга. Это делает SFENCE LFENCE
последовательность более мощная, чем простое объединение отдельных эффектов: эта последовательность также предотвращает переупорядочение StoreLoad (потому что загрузка не может пройти LFENCE
, который не может пройти SFENCE
, который не может пройти через хранилища), и, таким образом, представляет собой полный забор памяти (но также см. примечание (*) ниже). Обратите внимание, что порядок имеет значение здесь, и LFENCE SFENCE
Последовательность не имеет такого же синергетического эффекта.
Однако пока можно сказать, что MFENCE ~ SFENCE LFENCE
а также LFENCE ~ NOP
, это не значит MFENCE ~ SFENCE
, Я намеренно использую эквивалентность (~), а не равенство (=), чтобы подчеркнуть, что арифметические правила здесь не применяются. Взаимное влияние SFENCE
с последующим LFENCE
имеет значение; даже если грузы не переупорядочены друг с другом, LFENCE
требуется для предотвращения переупорядочения грузов с SFENCE
,
(*) Было бы правильно сказать, что MFENCE
сильнее, чем комбинация двух других заборов. В частности, примечание к CLFLUSH
Инструкция во втором томе руководства Intel гласит, что CLFLUSH
заказывается только MFENCE
инструкция. Не гарантируется, что он будет заказан другими инструкциями по фехтованию или сериализации или другим CLFLUSH
инструкция ".
(Обновить, clflush
теперь определяется как строго упорядоченный (как обычный магазин, так что вам нужно только mfence
если вы хотите заблокировать более поздние загрузки), но clflushopt
слабо упорядочен, но может быть огорожен sfence
.)
Рассмотрим следующий сценарий - это критический случай, когда выполнение спекулятивной нагрузки теоретически может нанести вред последовательной согласованности.
изначально [x]=[y]=0
CPU0: CPU1:
store [x]<--1 store [y]<--1
load r1<--[y] load r2<--[x]
Поскольку x86 позволяет переупорядочивать нагрузки с более ранними хранилищами на разные адреса, обе загрузки могут возвращать нули. Добавление одного lfence после каждого хранилища не помешает этому, поскольку они предотвращают переупорядочение только в одном и том же контексте, но, поскольку хранилища отправляются после удаления, вы можете иметь как lfences, так и обе нагрузки перед тем, как хранилища будут выполнены и просмотрены.
С другой стороны, mfence заставит хранилища работать и только после этого позволит выполнить загрузку, так что вы увидите обновленные данные хотя бы в одном контексте.
Что касается защиты - как указано в комментарии, теоретически она недостаточно сильна, чтобы предотвратить переупорядочение нагрузки над ней, поэтому она все равно может прочитать устаревшие данные. Хотя это верно в том, что касается официальных правил упорядочения памяти, я считаю, что текущая реализация x86 uarch делает его немного сильнее (я полагаю, не делая этого в будущем). Согласно этому описанию:
Из-за строгой модели упорядочения x86 буфер загрузки отслеживается трафиком когерентности. Удаленное хранилище должно сделать недействительными все остальные копии строки кэша. Если строка кэша считывается загрузкой, а затем становится недействительной удаленным хранилищем, загрузка должна быть отменена, поскольку она потенциально может считывать недействительные данные. Модель памяти x86 не требует отслеживания буфера хранилища.
Следовательно, любая нагрузка, еще не зафиксированная в машине, должна отслеживаться хранилищами из других ядер, тем самым делая эффективное время наблюдения нагрузки в точке фиксации, а не в точке выполнения (которая действительно вышла из строя и могла быть выполнена). намного раньше). Фиксация выполняется по порядку, и, следовательно, нагрузка должна соблюдаться после предыдущих инструкций, что делает бесполезными ограничения, как я уже говорил выше в комментариях, поскольку согласованность может поддерживаться таким же образом без них. Это в основном спекуляция, попытка объяснить общую концепцию, согласно которой в x86 смыслы бессмысленны - я не совсем уверен, откуда они возникли и есть ли другие соображения под рукой - был бы рад любому эксперту одобрить / оспорить эту теорию.
Все вышеперечисленное относится только к типам WB mem.
Конечно, это имеет смысл!
LFENCE
из таблицы данных Intel:
Выполняет операцию сериализации для всех инструкций загрузки из памяти, которые были выполнены до инструкции LFENCE. Эта операция сериализации гарантирует, что каждая команда загрузки, которая предшествует в программном порядке команде LFENCE, будет видна глобально, прежде чем любая команда загрузки, которая следует за инструкцией LFENCE, будет видна глобально.
Инструкция по записи в память типа MOV
являются атомными, если они правильно выровнены. Но эта инструкция обычно выполняется в кэше ЦП и не будет видна глобально в данный момент для всех других потоков, потому что память LFENCE/SFENCE or MFENCE
должен быть предварительно сформирован.
Типичный случай:
Если средство записи потоков разблокирует область памяти с помощью инструкции записи, подобной выравниванию памяти MOV
так что нет LOCK
инструкция префикса используется, чем строка кэша, где была выполнена MOV
Должен быть виден в очень коротком будущем всем другим темам. LFENCE
убедитесь в том, что читателю потока все остальные строки кэша от средства записи потока доступны глобально!