Почему (или нет?) SFENCE + LFENCE эквивалентно MFENCE?
Как мы знаем из предыдущего ответа на вопрос: делает ли он какую-то смысл инструкцией LFENCE в процессорах x86 / x86_64? что мы не можем использовать SFENCE
вместо MFENCE
для последовательной согласованности.
Ответ там предполагает, что MFENCE
= SFENCE
+ LFENCE
, то есть LFENCE
делает что-то, без которого мы не можем предоставить последовательную согласованность.
LFENCE
делает невозможным переупорядочение:
- Плавающая точка и целочисленные вычисления на современном оборудовании
- Что означают префиксы E и R в именах 32-битных и 64-битных регистров Intel?
- нажимать на 64-битную ОСОС
- Какова цель инструкции «ПАУЗА» в x86?
- Печать целого числа в виде строки с синтаксисом AT & T с системными вызовами Linux вместо printf
SFENCE LFENCE MOV reg, [addr]
– To ->
MOV reg, [addr] SFENCE LFENCE
Например, переупорядочение MOV [addr], reg
LFENCE
-> LFENCE
MOV [addr], reg
обеспечивается механизмом – Store Buffer , который переупорядочивает Store-Loads для повышения производительности, и LFENCE
не мешает ему. И SFENCE
отключает этот механизм .
Какой механизм отключает LFENCE
чтобы сделать невозможное переупорядочение (x86 не имеет механизма – Invalidate-Queue)?
И является ли переупорядочение SFENCE
MOV reg, [addr]
-> MOV reg, [addr]
SFENCE
возможно только в теории или, возможно, в реальности? И если это возможно, на самом деле, какие механизмы, как это работает?
- gfortran для макетов: что делает mcmodel = medium?
- Постройте Multiarch OpenSSL на OS X
- Адресная каноническая форма и арифметика указателя
- Как напечатать одноточечный поплавок с printf
- Почему GCC не использует частичные регистры?
- Почему неуправляемый доступ к памяти mmap'а иногда может возникнуть на AMD64?
- Потерянные циклы на Intel? Несоответствие между rdtsc и CPU_CLK_UNHALTED.REF_TSC
- Может ли x86 переупорядочить узкий магазин с более широкой нагрузкой, которая полностью его содержит?
SFENCE + LFENCE не блокирует переупорядочение StoreLoad, поэтому его недостаточно для последовательной согласованности . Это mfence
только mfence
(или операция lock
или настоящая инструкция сериализации, такая как cpuid
). См. Переупорядочение памяти Джеффа Прешинга, занесенное в закон, для случая, когда достаточно полного барьера.
Из справочного руководства по sfence
инструкций Intel для sfence
:
Процессор гарантирует, что каждый магазин до SFENCE будет глобально видимым перед любым хранилищем после того, как SFENCE станет глобально видимым.
но
Он не упорядочен по отношению к нагрузкам памяти или инструкции LFENCE.
LFENCE заставляет более ранние инструкции «заполнять локально» (то есть удаляться из нестандартной части ядра), но для хранилища или SFENCE, что просто означает помещение данных или маркера в буфер порядка памяти, а не очищение его так магазин становится глобально видимым. т.е. «завершение» SFENCE (выход из ROB) не включает очистку буфера хранилища.
Это похоже на то, что описано в разделе «Преширование» в «Блокировки памяти», как операции управления версиями, где барьеры StoreStore не являются «мгновенными». Позже в этой статье он объясняет, почему барьер #StoreStore + #LoadLoad + aLoadStore не соответствует барьеру #StoreLoad. (x86 LFENCE имеет некоторую дополнительную сериализацию streamа команд, но поскольку он не очищает буфер хранилища, аргументы все еще сохраняются).
LFENCE не полностью сериализуется, как cpuid
( который так же сильно mfence
памятью как mfence
или инструкция mfence
). Это просто LoadLoad + LoadStore барьер, а также некоторые элементы сериализации исполнения, которые, возможно, начинаются как детали реализации, но теперь закреплены в качестве гарантии, по крайней мере, на процессорах Intel. Это полезно с помощью rdtsc
, а также для избежания спекуляции ветвей, чтобы смягчить Spectre.
BTW, SFENCE – это no-op, за исключением магазинов NT; он заказывает их в отношении обычных (выпущенных) магазинов. Но не в отношении нагрузок или LFENCE. Только на CPU, который обычно слабо упорядочен, барьер магазина-магазина делает что-либо.
Реальная проблема заключается в переупорядочении StoreLoad между магазином и загрузкой, а не между магазином и барьерами, поэтому вам следует посмотреть на корпус с магазином, затем на барьер, затем на загрузку .
mov [var1], eax sfence lfence mov eax, [var2]
могут стать глобально видимыми (т. е. зафиксировать кеш L1d) в следующем порядке:
lfence mov eax, [var2] ; load stays after LFENCE mov [var1], eax ; store becomes globally visible before SFENCE sfence ; can reorder with LFENCE
В общем, MFENCE! = SFENCE + LFENCE. Например, приведенный ниже код при компиляции с -DBROKEN
не работает на некоторых системах Westmere и Sandy Bridge, но, похоже, работает над Ryzen. На самом деле в системах AMD достаточно СФЕРА, как представляется, достаточно.
#include #include #include #include using namespace std; #define ITERATIONS (10000000) class minircu { public: minircu() : rv_(0), wv_(0) {} class lock_guard { minircu& _r; const std::size_t _id; public: lock_guard(minircu& r, std::size_t id) : _r(r), _id(id) { _r.rlock(_id); } ~lock_guard() { _r.runlock(_id); } }; void synchronize() { wv_.store(-1, std::memory_order_seq_cst); while(rv_.load(std::memory_order_relaxed) & wv_.load(std::memory_order_acquire)); } private: void rlock(std::size_t id) { rab_[id].store(1, std::memory_order_relaxed); #ifndef BROKEN __asm__ __volatile__ ("mfence;" : : : "memory"); #else __asm__ __volatile__ ("sfence; lfence;" : : : "memory"); #endif } void runlock(std::size_t id) { rab_[id].store(0, std::memory_order_release); wab_[id].store(0, std::memory_order_release); } union alignas(64) { std::atomic rv_; std::atomic rab_[8]; }; union alignas(8) { std::atomic wv_; std::atomic wab_[8]; }; }; minircu r; std::atomic shared_values[2]; std::atomic*> pvalue(shared_values); std::atomic total(0); void r_thread(std::size_t id) { uint64_t subtotal = 0; for(size_t i = 0; i < ITERATIONS; ++i) { minircu::lock_guard l(r, id); subtotal += (*pvalue).load(memory_order_acquire); } total += subtotal; } void wr_thread() { for (size_t i = 1; i < (ITERATIONS/10); ++i) { std::atomic* o = pvalue.load(memory_order_relaxed); std::atomic * p = shared_values + i % 2; p->store(1, memory_order_release); pvalue.store(p, memory_order_release); r.synchronize(); o->store(0, memory_order_relaxed); // should not be visible to readers } } int main(int argc, char* argv[]) { std::vector vec_thread; shared_values[0] = shared_values[1] = 1; std::size_t readers = (argc > 1) ? ::atoi(argv[1]) : 8; if (readers > 8) { std::cout << "maximum number of readers is " << 8 << std::endl; return 0; } else std::cout << readers << " readers" << std::endl; vec_thread.emplace_back( [=]() { wr_thread(); } ); for(size_t i = 0; i < readers; ++i) vec_thread.emplace_back( [=]() { r_thread(i); } ); for(auto &i: vec_thread) i.join(); std::cout << "total = " << total << ", expecting " << readers * ITERATIONS << std::endl; return 0; }
Какой механизм отключает LFENCE, чтобы сделать невозможное переупорядочение (x86 не имеет механизма – Invalidate-Queue)?
В руководствах Intel, том 2A, стр. 3-464 документации для инструкции LFENCE
:
LFENCE не выполняется до тех пор, пока все предыдущие инструкции не будут завершены локально, и никакая более поздняя инструкция не начнет выполнение до тех пор, пока LFENCE не завершится
Так что да, ваш пример переупорядочения явно запрещен инструкцией LFENCE
. Второй пример, включающий только команды SFENCE
является допустимым переупорядочением, поскольку SFENCE
не влияет на операции загрузки.