Подтвердить что ты не робот

Что лучше барьера для записи на x86: lock + addl или xchgl?

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

Цитата из руководств IA32 (Том 3A, глава 8.2: Заказ памяти):

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

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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


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

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

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

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

Ответ 5

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

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