Какой барьер для записи лучше для x86: lock+addl или xchgl?

Ядро Linux использует lock; addl $0,0(%%esp) в качестве барьера записи, в то время как библиотека RE2 использует xchgl (%0),%0 как написать барьер. Какая разница и что лучше?

X86 также требует инструкции по чтению барьера? RE2 определяет свою функцию барьера чтения как запрет на x86, в то время как Linux определяет ее как lfence или не работает в зависимости от того, доступен ли SSE2. Когда lfence требуется?

5 ответов

Решение

" Блокировка; addl $ 0,0 (%%esp) " быстрее в случае, если мы тестируем переменную состояния блокировки 0 по адресу (%%esp). Потому что мы добавляем 0 значение для блокировки переменной и нулевой флаг устанавливается в 1, если значение блокировки переменной по адресу (%%esp) равно 0.

Защита от данных Intel:

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

Например: инструкция записи в память, такая как 'mov', является атомарной (им не нужен префикс блокировки), если они правильно выровнены. Но эта инструкция обычно выполняется в кэше ЦП и не будет видна глобально в данный момент для всех других потоков, потому что ограничение памяти должно выполняться в первую очередь.

РЕДАКТИРОВАТЬ:

Таким образом, основное различие между этими двумя инструкциями заключается в том, что инструкция xchgl не будет влиять на условные флаги. Конечно, мы можем проверить состояние переменной блокировки с помощью инструкции lock cmpxchg, но это все же более сложно, чем с помощью инструкции add add $0.

Цитата из руководств IA32 (Том 3А, Глава 8.2: Упорядочение памяти):

В однопроцессорной системе для областей памяти, определенных как кэшируемые с обратной записью, модель упорядочения памяти соблюдает следующие принципы [..]

  • Чтения не переупорядочиваются с другими чтениями
  • Записи не переупорядочиваются со старыми чтениями
  • Записи в память не переупорядочиваются с другими записями, за исключением
    • записи выполнены с CLFLUSH инструкция
    • потоковые хранилища (записи), выполняемые с временными инструкциями перемещения ([список инструкций здесь])
    • строковые операции (см. раздел 8.2.4.1)
  • Чтения могут быть переупорядочены со старыми записями в разных местах, но не со старыми записями в том же месте.
  • Чтение или запись не могут быть переупорядочены с инструкциями ввода / вывода, заблокированными инструкциями или инструкциями сериализации
  • Читает не может пройти LFENCE а также MFENCE инструкции
  • Пишет не может пройти SFENCE а также MFENCE инструкции

Примечание. Вышеуказанное "в однопроцессорной системе" вводит в заблуждение. Те же правила действуют для каждого (логического) процессора в отдельности; далее в руководстве описываются дополнительные правила упорядочения между несколькими процессорами. Единственное, что касается этого вопроса, это то, что

  • Заблокированные инструкции имеют полный порядок.

Короче говоря, до тех пор, пока вы пишете в память с обратной записью (то есть всю память, которую вы когда-либо увидите, пока вы не являетесь драйвером или графическим программистом), большинство инструкций x86 практически последовательны - единственное переупорядочение процессор x86 может выполнить переупорядочение более поздних (независимых) операций чтения перед выполнением операций записи. Главное в барьерах записи состоит в том, что они имеют lock префикс (неявный или явный), который запрещает все переупорядочения и гарантирует, что все процессоры в многопроцессорной системе видят операции в одном и том же порядке.

Кроме того, в памяти с обратной записью чтение никогда не переупорядочивается, поэтому нет необходимости в барьерах чтения. Последние процессоры x86 имеют более слабую модель согласованности памяти для потоковых хранилищ и памяти с комбинированной записью (обычно используется для отображения графической памяти). Вот где различные fence инструкции вступают в игру; они не нужны для любого другого типа памяти, но некоторые драйверы в ядре Linux имеют дело с памятью с комбинированной записью, поэтому они просто определяют свой барьер чтения таким образом. Список упорядоченных моделей для каждого типа памяти приведен в разделе 11.3.1 в вып. 3А руководств IA-32. Краткая версия: Write-Through, Write-Back и Write-Protected позволяют спекулятивное чтение (следуя правилам, описанным выше), Uncachable и Strong Uncacheable память имеет строгие гарантии упорядочения (нет переупорядочения процессора, операции чтения / записи выполняются немедленно, используются для MMIO) и запись Комбинированная память имеет слабое упорядочение (то есть ослабленные правила упорядочения, которые нуждаются в ограждениях)

lock addl $0, (%esp) является заменой mfenceне lfence,

Вариант использования - когда вам нужно заблокировать переупорядочение StoreLoad (единственный вид, который позволяет модель сильной памяти x86), но вам не нужна атомарная операция RMW с общей переменной. https://preshing.com/20120515/memory-reordering-caught-in-the-act/

например, предполагая, выровнены std::atomic<int> a,b:

movl   $1, a             a = 1;    Atomic for aligned a
# barrier needed here
movl   b, %eax           tmp = b;  Atomic for aligned b

Ваши варианты:

  • Создайте хранилище последовательной согласованности сxchgнапример, mov $1, %eax / xchg %eax, a так что вам не нужен отдельный барьер; это часть магазина. Я думаю, что это самый эффективный вариант на самом современном оборудовании; Компиляторы C++11, кроме использования gcc xchg для магазинов seq_cst.
  • использование mfence как барьер. (GCC использует mov + mfence для магазинов seq_cst).
  • использование lock addl $0, (%esp) как барьер. любой lockИнструкция является полным барьером. Есть ли у блокировки xchg такое же поведение, как у mfence?

    (Или в каком-то другом месте, но в L1d стек почти всегда приватный и горячий, так что это несколько хороший кандидат. Однако это может создать цепочку зависимостей для чего-то, использующего данные в нижней части стека.)

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

По возможности, используя xchg для seq-cst хранилища, вероятно, лучше, хотя он также читает из общего расположения. mfence медленнее, чем ожидалось, на последних процессорах Intel ( загружает и сохраняет ли единственные инструкции, которые переупорядочиваются?), также блокирует неупорядоченное выполнение независимых инструкций, не связанных с памятью lfence делает.

Это может даже стоить использовать lock addl $0, (%esp)/(%rsp) вместо mfence даже когда mfence доступно, но я не экспериментировал с минусами. С помощью -64(%rsp) или что-то может снизить вероятность зависимости данных от чего-то горячего (локальный или обратный адрес), но это может сделать такие инструменты, как valgrind, несчастными.


lfenceникогда не используется для упорядочения памяти, если вы не читаете из видеопамяти RAM (или другой слабо упорядоченной области WC) с загрузками MOVNTDQA.

Сериализация неупорядоченного выполнения (но не буфера хранилища) бесполезна для остановки переупорядочения StoreLoad (единственный вид, который модель сильной памяти x86 допускает для нормальных областей памяти WB (с обратной записью)).

Реальные сценарии использования для lfence предназначены для блокировки не по порядку исполнения rdtsc для синхронизации очень коротких блоков кода или для ослабления Спектра, блокируя спекуляции через условную или косвенную ветвь.

Смотрите также, Когда я должен использовать _mm_sfence _mm_lfence и _mm_mfence (мой ответ и ответ @BeeOnRope), чтобы узнать больше о том, почему lfence не полезно, и когда использовать каждую из инструкций барьера. (Или в моем, C++ при программировании на C++ вместо asm).

В дополнение к другим ответам разработчики HotSpot обнаружили, что lock; addl $0,0(%%esp) с нулевым смещением может быть неоптимальным, на некоторых процессорах это может вводить ложные зависимости данных; связанная ошибка jdk.

Касание местоположения стека с другим смещением может улучшить производительность при некоторых обстоятельствах.

Важная часть lock; addl а также xchgl это lock префикс. Это неявно для xchgl, Там действительно нет никакой разницы между ними. Я бы посмотрел, как они собираются, и выбрал тот, который короче (в байтах), поскольку обычно он быстрее для эквивалентных операций на x86 (отсюда и такие хитрости, как xorl eax,eax)

Присутствие SSE2, вероятно, является просто прокси для реального состояния, которое в конечном итоге является функцией cpuid, Вероятно, получается, что SSE2 подразумевает существование lfence и наличие SSE2 проверялось / кэшировалось при загрузке. lfence требуется, когда это доступно.

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