Если вы используете NT-магазины, вам может потребоваться _mm_sfence
или, может быть, даже _mm_mfence
.Варианты использования для _mm_lfence
гораздо более неясны.
Если нет, просто используйте C ++ 11 std :: atomic и позвольте компилятору беспокоиться о деталях asm управления упорядочением памяти.
x86 имеет строго упорядоченную модель памяти, но C ++ имеет очень слабую модель памяти (то же самое для C). Для семантики получения / выпуска вам нужно только предотвратить время компиляции переупорядочение .См. Статью Джеффа Прешинга Упорядочение памяти во время компиляции статья.
_mm_lfence
и _mm_sfence
действительно имеют необходимый эффект барьера компилятора, но они также приведут к тому, что компилятор испустит бесполезный lfence
или sfence
asm-инструкция, которая замедляет выполнение вашего кода.
Существуют лучшие варианты для управления переупорядочением во время компиляции, когда вы не выполняете какие-то непонятные вещи, которые могут заставить вас захотеть sfence
,
Например, GNU C / C ++ asm("" ::: "memory")
является барьером компилятора (все значения должны находиться в памяти, совпадающей с абстрактной машиной из-за "memory"
clobber), но инструкции asm не выдаются.
Если вы используете C ++ 11 std :: atomic, вы можете просто сделать shared_var.store(tmp, std::memory_order_release)
.Это гарантированно станет глобально видимым после любых более ранних присвоений Си, даже неатомарным переменным.
_mm_mfence
потенциально полезно, если выпереход на собственную версию C11 / C ++ 11 std::atomic
, потому что фактическая инструкция mfence
- это один из способов получения последовательной согласованности, то есть прекращение последующими загрузками чтения значения до тех пор, пока предыдущие хранилища не станут глобально видимыми.См. Переупорядочение памяти, зафиксированное в законе Джеффа Прешинга .
Но обратите внимание, что mfence
на современном оборудовании кажется медленнее, чем при использовании заблокированной операции атомного RMW.Например, xchg [mem], eax
также является полным барьером, но работает быстрее и работает в магазине.В Skylake способ реализации mfence
предотвращает выполнение не по порядку даже следующих за ним инструкций без памяти.См. в нижней части этого ответа .
В C ++ без встроенного asm, однако, ваши параметры для барьеров памяти более ограничены ( Сколько инструкций по барьерам памяти имеет процессор x86?).mfence
не страшно, и это то, что gcc и clang в настоящее время используют для создания хранилищ последовательной согласованности.
Серьезно просто используйте C ++ 11 std :: atomic или C11 stdatomic, если это возможно;Его проще использовать, и вы получаете довольно хороший код для многих вещей.Или в ядре Linux, уже есть функции-оболочки для встроенного asm для необходимых барьеров.Иногда это просто барьер компилятора, иногда это также инструкция asm, чтобы получить более сильный порядок выполнения, чем по умолчанию.(например, для полного барьера).
Никакие барьеры не сделают ваши магазины более быстрыми.Все, что они могут сделать, это отложить более поздние операции в текущем потоке, пока не произойдут более ранние события.Процессор уже пытается зафиксировать не спекулятивные хранилища в кэш-памяти L1d как можно быстрее.
_mm_sfence
является наиболее вероятным барьером для фактического использования вручную в C ++
Основной сценарий использования _mm_sfence()
- после нескольких _mm_stream
сохранений, перед установкой флага, который будут проверять другие потоки.
См. Enhanced REP MOVSB для memcpy для получения дополнительной информации о хранилищах NT.по сравнению с обычными магазинами и пропускной способностью памяти x86.Для записи очень больших буферов (больше, чем размер кэша L3), которые определенно не будут перечитаны в ближайшее время, может быть хорошей идеей использовать хранилища NT.
NT-магазины слабо упорядочены, в отличие от обычных магазинов, поэтому вам нужно sfence
, если , вы заботитесь о публикации данных в другом потоке. Если нет (вы в конечном итоге прочитаете их из этой темы), то нет.Или, если вы делаете системный вызов перед тем, как сообщить другому потоку, что данные готовы, это также сериализует.
sfence
(или какой-либо другой барьер) необходим, чтобы дать вам синхронизацию освобождения / получения при использовании хранилищ NT. Реализации C ++ 11 std::atomic
предоставляют вам возможность защитить свои хранилища NT , чтобы атомарные хранилища релизов могли быть эффективными.
#include <atomic>
#include <immintrin.h>
struct bigbuf {
int buf[100000];
std::atomic<unsigned> buf_ready;
};
void producer(bigbuf *p) {
__m128i *buf = (__m128i*) (p->buf);
for(...) {
...
_mm_stream_si128(buf, vec1);
_mm_stream_si128(buf+1, vec2);
_mm_stream_si128(buf+2, vec3);
...
}
_mm_sfence(); // All weakly-ordered memory shenanigans stay above this line
// So we can safely use normal std::atomic release/acquire sync for buf
p->buf_ready.store(1, std::memory_order_release);
}
Тогда потребитель может безопасно сделать if(p->buf_ready.load(std::memory_order_acquire)) { foo = p->buf[0]; ... }
без какой-либо гонки данных. Неопределенное поведение. Читающая сторона не нуждается _mm_lfence
; Слабоупорядоченная природа NT-хранилищ ограничена только ядром, выполняющим написание. Как только он становится видимым в глобальном масштабе, он становится полностью связным и упорядоченным в соответствии с обычными правилами.
Другие варианты использования включают порядок clflushopt
для управления порядком хранения данных в энергонезависимом хранилище с отображением в памяти. (например, сейчас существует NVDIMM, использующий память Optane, или модули DIMM с DRAM с резервным питанием от аккумулятора.)
_mm_lfence
почти никогда не используется в качестве фактического ограждения . Нагрузки могут быть слабо упорядочены только при загрузке из областей памяти WC (Write-Combining), таких как видеопамять. Даже movntdqa
(_mm_stream_load_si128
) по-прежнему строго упорядочен в обычной (WB = обратной записи) памяти и ничего не делает для уменьшения загрязнения кэша. (prefetchnta
может, но это трудно настроить и может ухудшить ситуацию.)
TL: DR: если вы не пишете графические драйверы или что-то еще, что напрямую отображает видеопамять, вам не нужно _mm_lfence
, чтобы упорядочивать свои нагрузки.
lfence
обладает интересным микроархитектурным эффектом, заключающимся в предотвращении выполнения более поздних инструкций до тех пор, пока он не будет удален. например остановить _rdtsc()
от считывания счетчика циклов, пока в микробенчмарке еще не завершена более ранняя работа. (Применяется всегда на процессорах Intel, но только на AMD с настройкой MSR: Сериализует ли LFENCE на процессорах AMD? . В противном случае lfence
работает 4 раза в такт на семействе Bulldozer, поэтому явно не сериализуется.)
Поскольку вы используете встроенные функции из C / C ++, компилятор генерирует код для вас. У вас нет прямого контроля над asm, но вы можете использовать _mm_lfence
для таких вещей, как смягчение Specter, если вы можете заставить компилятор поместить его в нужное место в выводе asm: сразу после условной ветви, перед двойной массив доступа. (как foo[bar[i]]
). Если вы используете патчи ядра для Spectre, я думаю, что ядро защитит ваш процесс от других процессов, поэтому вам нужно беспокоиться об этом только в программе, которая использует изолированную программную среду JIT и беспокоится о том, что на нее будут нападать изнутри. песочница.