Почему SFENCE + LFENCE эквивалентно (или нет?) MFENCE?

Как мы знаем из предыдущего ответа на вопрос Имеет ли смысл инструкция LFENCE в процессорах x86 / x86_64?, что мы не можем использовать SFENCE вместо MFENCE для последовательной согласованности.

Ответ там предполагает, что MFENCE = _4 _ + _ 5_, т.е. что LFENCE делает что-то, без чего мы не можем обеспечить последовательную согласованность.

LFENCE делает невозможным повторный заказ:

SFENCE
LFENCE
MOV reg, [addr]

-- To -->

MOV reg, [addr]
SFENCE
LFENCE

Например, переупорядочивание MOV [addr], reg LFENCE -> LFENCE MOV [addr], reg обеспечивается механизмом - Буфер хранилища, который переупорядочивает Магазин - Загрузки для повышения производительности, и потому LFENCE не препятствует этому. И SFENCE отключает этот механизм.

Какой механизм отключает LFENCE, чтобы сделать невозможным переупорядочивание (x86 не имеет механизма - Invalidate-Queue)?

И возможен ли переупорядочение SFENCE MOV reg, [addr] -> MOV reg, [addr] SFENCE только теоретически или, возможно, в реальности? А если возможно, то какие в реальности механизмы, как это работает?


person Alex    schedule 23.12.2014    source источник
comment
Я предполагаю, что L / S / M FENCE соблюдаются memory controller. Заборы используются для координации системной памяти и кэш-памяти. И я думаю, что за согласованность кэша отвечает memory controller.   -  person Peng Zhang    schedule 30.12.2014
comment
@Peng Zhang Согласованность кэша, обеспечиваемая автоматически CC-протоколами MOESI / MESIF, в частности этими протоколами, обеспечивает согласованность получения и выпуска. Насколько я знаю, L/S/MFENCE не имеет отношения к когерентности кеша, потому что SFENCE очищает буфер хранилища, который не имеет отношения к когерентности кеша. В некоторых процессорах (не x86) Load FENCE flush Invalidate-Queue, но x86 не имеет этого. В Интернете я обнаружил, что LFENCE не имеет смысла в процессорах x86, т.е. ничего не делает. Тогда переупорядочивание SFENCE MOV reg, [addr] - ›MOV reg, [addr] SFENCE возможно только теоретически, а не, возможно, в действительности, правда?   -  person Alex    schedule 30.12.2014


Ответы (3)


Инструкции по ограждению x86 можно кратко описать следующим образом:

  • MFENCE предотвращает глобальное наблюдение за любой последующей загрузкой или сохранением перед любой более ранней загрузкой или сохранением. Он истощает буфер хранилища перед тем, как более поздние загрузки 1 могут выполнить.

  • LFENCE блокирует отправку инструкций (по терминологии Intel) до тех пор, пока не будут удалены все предыдущие инструкции. В настоящее время это реализуется путем осушения ROB (буфера повторного заказа) до того, как последующие инструкции смогут передать в серверную часть.

  • SFENCE упорядочивает хранилища только по отношению к другим хранилищам, то есть предотвращает фиксацию хранилищ NT из буфера хранилища перед самой SFENCE. Но в остальном SFENCE похожа на обычное хранилище, которое перемещается через буфер хранилища. Думайте об этом, как о том, как поставить разделитель на конвейерную ленту кассового магазина в продуктовом магазине, который не дает магазинам NT забираться раньше времени. Это не обязательно принудительно очищает буфер хранилища до того, как он удалится из ROB, поэтому установка LFENCE после этого не дает в сумме MFENCE.

  • Команда сериализации, такая как CPUID (и IRET и т. Д.), Истощает все (ROB, буфер хранилища) до того, как последующие инструкции могут быть переданы в серверную часть. MFENCE + LFENCE также сделает это, но настоящие инструкции сериализации могут иметь и другие эффекты, я не знаю.

Эти описания немного неоднозначны с точки зрения того, какие именно операции заказываются, и есть некоторые различия между поставщиками (например, SFENCE сильнее на AMD) и даже процессорами от одного поставщика. Обратитесь к руководству Intel и обновлениям спецификаций, а также к руководству AMD и руководствам по версиям для получения дополнительной информации. Есть также много других обсуждений этих инструкций в других местах. Но сначала прочтите официальные источники. Приведенные выше описания, я думаю, являются минимальным указанным на бумаге поведением поставщиков.

Сноска 1: OoO exec более поздних хранилищ не нужно блокировать с помощью MFENCE; их выполнение просто записывает данные в буфер хранилища. Фиксация по порядку уже заказывает их после предыдущих магазинов и фиксирует после списания заказов. загружает (потому что x86 требует, чтобы загрузка была завершена, а не только началась, прежде чем они могут быть отключены, как часть обеспечения порядка загрузки). Помните, что оборудование x86 построено так, чтобы запрещать переупорядочивание, отличное от StoreLoad.

В руководстве Intel, том 2, номер 325383-072US, SFENCE описывается как инструкция, которая гарантирует, что каждое хранилище до SFENCE будет глобально видимым до того, как любое хранилище после SFENCE станет глобально видимым. Раздел 11.10 тома 3 говорит, что буфер хранилища опустошается при использовании SFENCE. Правильная интерпретация этого утверждения - это в точности предыдущее утверждение из тома 2. Таким образом, можно сказать, что SFENCE истощает буфер хранилища в этом смысле. Нет гарантии, в какой момент в течение жизненного цикла SFENCE более ранние магазины достигнут GO. Для любого более раннего магазина это могло произойти до, во время или после прекращения работы SFENCE. Что касается смысла GO, это зависит от нескольких факторов. Это выходит за рамки вопроса. См .: Почему «movnti», за которым следует «sfence», гарантирует постоянное заказ?.

MFENCE действительно должен предотвращать переупорядочивание хранилищ NT с другими хранилищами, поэтому он должен включать все, что делает SFENCE, а также очищать буфер хранилища. А также переупорядочение слабо упорядоченных загрузок SSE4.1 NT из памяти WC, что сложнее, потому что обычные правила, которые получают бесплатный порядок загрузки, к ним больше не применяются. Гарантия этого - вот почему обновление микрокода Skylake усилило (и замедлило) MFENCE., чтобы слить ROB как LFENCE. MFENCE все еще может иметь меньший вес, чем аппарат с поддержкой HW для необязательно принудительного упорядочивания NT-нагрузок в конвейере.


Основная причина, по которой SFENCE + LFENCE не равно MFENCE, заключается в том, что SFENCE + LFENCE не блокирует переупорядочение StoreLoad, поэтому этого недостаточно для последовательной согласованности. Только mfence (или операция locked, или настоящая инструкция сериализации, такая как cpuid) сделает это. См. переупорядочение памяти, обнаруженное в действии Джеффа Прешинга, чтобы узнать случай, когда достаточно только полного барьера.


Из справочного руководства Intel для sfence:

Процессор гарантирует, что каждое хранилище до SFENCE будет глобально видимым до того, как любое хранилище после SFENCE станет глобально видимым.

но

Он не упорядочен по загрузке памяти или инструкции LFENCE.


LFENCE заставляет более ранние инструкции выполняться локально (т. Е. Удаляться из вышедшей из строя части ядра), но для хранилища или SFENCE это просто означает размещение данных или маркера в буфере порядка памяти, а не его очистку, поэтому хранилище становится видимым во всем мире. т.е. завершение SFENCE (вывод из ROB) не включает очистку буфера хранилища.

Это похоже на описание Preshing в Барьеры памяти похожи на операции управления исходным кодом., где препятствия StoreStore не возникают мгновенно. Позже в этой статье он объясняет, почему барьер #StoreStore + #LoadLoad + #LoadStore не суммируется с барьером #StoreLoad. (x86 LFENCE имеет некоторую дополнительную сериализацию потока инструкций, но, поскольку он не очищает буфер хранилища, рассуждения все еще остаются в силе).

LFENCE не полностью сериализуется, как cpuid ( который является таким же сильным барьером памяти, как mfence или locked инструкция). Это просто барьер LoadLoad + LoadStore плюс некоторые вещи сериализации выполнения, которые, возможно, начинались как деталь реализации, но теперь закреплены в качестве гарантии, по крайней мере, для процессоров Intel. Это полезно с rdtsc, а также для предотвращения спекуляций ветвями для смягчения последствий Spectre.


Кстати, SFENCE не работает для магазинов WB (обычных).

Он упорядочивает хранилища WC (например, movnt или записи в видеопамять) в отношении любых хранилищ, но не в отношении нагрузок или LFENCE. Только на ЦП, который обычно слабо упорядочен, барьер магазин-магазин делает что-нибудь для обычных магазинов. Вам не понадобится SFENCE, если вы не используете хранилища NT или область памяти, отображаемую в WC. Если бы он гарантировал очистку буфера хранилища до его выхода из эксплуатации, вы могли бы построить MFENCE из SFENCE + LFENCE, но это не относится к Intel.


Реальная проблема заключается в переупорядочивании 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
person Peter Cordes    schedule 14.05.2018
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат. - person Samuel Liew♦; 19.10.2019
comment
При выдаче магазина запись выделяется в ROB и SB. А когда магазин уйдет, его можно будет удалить из ROB. И после этого он в конечном итоге будет удален из SB, как только будет зафиксирован на L1D. Таким образом, магазин в SB может пережить магазин в ROB. Это верно? Я пытаюсь построить правильную мысленную модель, и у меня сложилось впечатление, что это ключ к лучшему пониманию заборов. - person pveentjer; 14.05.2020
comment
Вышеупомянутое должно быть так. В противном случае магазин может заблокировать весь конвейер, и это основная причина, по которой у нас вообще есть SB. - person pveentjer; 14.05.2020
comment
MFENCE ожидает, пока SB будет слит. LFENCE ждет, пока ROB будет осушен. Я правильно понимаю? - person pveentjer; 14.05.2020
comment
@pveentjer: Да, но вам также нужно указать, что блокируется во время ожидания. Для LFENCE это этап front-end. Для MFENCE, в зависимости от реализации, это может быть только exec для более поздних загрузок, с продолжением работы OoO exec для ALU. (То же самое для полного барьера как часть инструкции locked). Или для других реализаций (например, Skylake с обновлениями микрокода) MFENCE, по-видимому, блокирует интерфейс при истощении SB + ROB, например lock xor + LFENCE. См. Конец этого ответа - person Peter Cordes; 14.05.2020
comment
Превосходно. Я посмотрю поближе. Это начало обретать смысл, когда я понял, что ожидание слива SB - это не то же самое, что ожидание слива ROB. - person pveentjer; 14.05.2020
comment
@pveentjer: Действительно, IDK, почему я вообще не сказал этого в своем ответе; возможно, эти концепции не были так ясны в моей голове 2 года назад. Отредактировано, чтобы добавить новый раздел вверху. - person Peter Cordes; 14.05.2020
comment
Все инструкции по сериализации ждут, пока ROB будет очищен, прежде чем выдать следующую команду? - person pveentjer; 14.05.2020
comment
@pveentjer: Да, блокировать интерфейс до тех пор, пока буфер хранилища ROB и не будет исчерпан, скрывая все эффекты конвейерной обработки. Вот что означает сериализация как технический термин в руководствах по x86. Только несколько инструкций гарантированно будут такими, включая cpuid и iret. - person Peter Cordes; 14.05.2020
comment
Вот почему LFENCE не является полностью сериализуемой инструкцией; он ждет только слива ROB, но не SB. - person pveentjer; 14.05.2020
comment
В документации Intel говорится о сериализации инструкций в сочетании с параллельным выполнением инструкций: xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/ Имеются ли в виду конвейерная обработка или суперскалярное выполнение и выполнение вне очереди? - person pveentjer; 16.05.2020
comment
В случае MFENCE ... после того, как нагрузка обнаружена, никакие дальнейшие инструкции не принимаются от ROB и отправляются в RS до тех пор, пока SB не будет опорожнен. Таким образом, как только эта нагрузка встречается, дальнейшие инструкции не выполняются. независимо от того, независимы ли они ... все останавливается ... Это правильно? Извините, что задаю эти «базовые» вопросы, но хочу убедиться, что я правильно понимаю. - person pveentjer; 16.05.2020
comment
@pveentjer: инструкции сериализации должны были бы истощить SB на скалярном конвейере по порядку, но, возможно, фактически не опустошить сам конвейер. Сериализация действительно гарантирует, что самомодифицирующийся код виден, поэтому конвейер, который не отслеживает адреса хранения на предмет нахождения рядом с инструкциями в полете, должен будет отбросить предварительную выборку инструкций и не получать ничего, кроме CPUID или w / e, до тех пор, пока SB был осушен. Я думаю, вы могли бы сказать, что инструкции сериализации - это барьеры памяти, которые воспринимают выборку кода как нагрузку. Это один из гарантированных эффектов. (Современные процессоры отслеживают и могут не сбрасывать) - person Peter Cordes; 16.05.2020
comment
@pveentjer: Нет, MFENCE не должен блокировать OoO exec независимой работы ALU после себя или после загрузки. Если работа зависит от нагрузки, она будет заблокирована правилами зависимости, но если она зависит только от регистров, которые были записаны до MFENCE, тогда никакие правила не остановят ЦП от продолжения. Только возможные детали реализации. (Я ожидаю, что на процессорах без MFENCE, в основном сериализирующего Skylake, независимые инструкции ALU после более поздних загрузок все еще могут выполняться). - person Peter Cordes; 16.05.2020
comment
Мне нужно еще немного покопаться в механизме исполнения. Каждый ответ приводит к еще 2 вопросам :) - person pveentjer; 16.05.2020
comment
Я немного подумал над этой темой. И имеет смысл продолжать выполнение инструкции; нужно только глохнуть грузы; и все зависимые инструкции будут автоматически заблокированы, так как в этом вся цель алгоритма Томасуло. - person pveentjer; 16.05.2020
comment
@PeterCordes Я читал Барьеры памяти как источник Операции управления ... Есть идеи, почему он сравнивает барьер загрузки-загрузки с извлечением , которое не извлекает из последней версии центрального репозитория. Почему он это заявляет? Какое поведение реальных процессоров он хочет продемонстрировать с помощью этого? - person Suraaj K S; 07.08.2020
comment
Довольно поздно для вечеринки, но не следует ли читать первую фразу: MFENCE истощает буфер хранилища, прежде чем последующие загрузки и хранилища смогут выполняться.? Кроме того, если верить руководству Intel, sfence истощает буфер хранилища, но модель маркера имеет больше смысла. Не помню, где я это читал (наверное, от вас), вы знали, откуда это? - person Margaret Bloom; 15.10.2020
comment
@MargaretBloom: при выполнении хранилища просто записывается в буфер хранилища, поэтому нет, ничего страшного, если mfence не блокирует это. Фиксация по порядку уже заказывает более поздние магазины после любых предыдущих. И зафиксировать после выхода на пенсию заказов их после любых более ранних загрузок. На практике реализации могут сделать mfence сильнее, например Skylake с обновленным микрокодом, полностью блокирующим проблему и отправкой всего после mfence. Re: модель маркера: да, это, вероятно, от меня, на основе Является ли барьер памяти инструкцией, которую выполняет процессор, или это просто маркер ? - person Peter Cordes; 16.10.2020
comment
@MargaretBloom: Или, что более вероятно, я подхватил эту идею при ответе Действует ли барьер памяти одновременно как маркер и как инструкция? - person Peter Cordes; 16.10.2020
comment
@MargaretBloom: обновил свой ответ, я забыл о хранилищах NT и загрузках NT в моем предыдущем комментарии. Вот почему MFENCE так медленно работает в Skylake. Re: модель маркера для SFENCE (или для упорядочивания загрузки NT с помощью MFENCE): я не знаю наверняка, как выглядит внутренняя реализация, но мы знаем, что на практике SFENCE + LFENCE не складывается с MFENCE даже для обычных магазинов / простых загрузок (например, другой ответ на этот вопрос). Но это было бы, если бы он опустошил буфер хранилища до того, как удалился. Так что я думаю, что как ментальная модель она работает достаточно хорошо, по крайней мере, для анализа безопасности. - person Peter Cordes; 16.10.2020

В общем случае MFENCE! = SFENCE + LFENCE. Например, приведенный ниже код при компиляции с -DBROKEN дает сбой в некоторых системах Westmere и Sandy Bridge, но, похоже, работает на Ryzen. Фактически, в системах AMD достаточно просто SFENCE.

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
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<uint64_t>           rv_;
                        std::atomic<unsigned char>      rab_[8];
                };
                union alignas(8) {
                        std::atomic<uint64_t>           wv_;
                        std::atomic<unsigned char>      wab_[8];
                };
};

minircu r;

std::atomic<int> shared_values[2];
std::atomic<std::atomic<int>*> pvalue(shared_values);
std::atomic<uint64_t> 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<int>* o = pvalue.load(memory_order_relaxed);
                std::atomic<int>* 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<std::thread> 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;
}
person Alexander Korobka    schedule 14.05.2018
comment
похоже, не имеет никакого эффекта - person Alexander Korobka; 25.07.2018
comment
Александр, к вашему сведению, StackExchange по какой-то причине требует, чтобы вы поместили новую строку между подсказкой языка и кодовым блоком, см. Историю изменений для получения дополнительной информации, Росс Ридж позаботился об этом. - person jrh; 25.07.2018
comment
По какой-то причине AMD определяет sfence как полный барьер, очищающий буфер хранилища до того, как последующие загрузки смогут выполняться. Я думаю, что это официально задокументировано для процессоров AMD, а не просто детали реализации, такие как sfence, происходящее с утечкой SB, прежде чем он сможет выйти из ROB. - person Peter Cordes; 14.05.2020

Какой механизм отключает LFENCE, чтобы сделать невозможным переупорядочение (x86 не имеет механизма - Invalidate-Queue)?

Из документации Intel, том 2A, стр. 3-464, LFENCE инструкция:

LFENCE не выполняется до тех пор, пока все предыдущие инструкции не будут выполнены локально, и никакая последующая инструкция не начнет выполнение, пока LFENCE не завершится.

Итак, да, в вашем примере переупорядочение явно запрещено инструкцией LFENCE. Ваш второй пример, включающий только SFENCE инструкции, ЯВЛЯЕТСЯ допустимым переупорядочением, поскольку SFENCE не влияет на операции загрузки.

person Myles Hathcock    schedule 10.04.2015
comment
Спасибо! Но я не утверждаю, что MFENCE = LFENCE + SFENCE, я утверждаю, что MFENCE = SFENCE + LFENCE - порядок барьеров важен, вы можете увидеть наше обсуждение: stackoverflow.com/questions/20316124 / SFENCE + LFENCE нельзя переупорядочить на LFENCE + SFENCE, поэтому 2 mov [mem], reg не может выполняться после SFENCE и 3 mov reg, [mem] не может выполняться до LFENCE, не может быть переупорядочен: 1 mov reg, [mem] 2 mov [mem], reg SFENCE LFENCE 3 mov reg, [mem] 4 mov [mem], reg - person Alex; 10.04.2015
comment
@Alex Вы абсолютно правы, извините за ошибку. Я удалил эту часть своего ответа. Я хотел бы исследовать эту минуту более подробно, я опубликую ссылку здесь, как только закончу свою рецензию. - person Myles Hathcock; 10.04.2015
comment
Хорошо, не волнуйтесь, я тоже совершил ту же ошибку в начале обсуждения по ссылке :) Может это не простой вопрос. - person Alex; 10.04.2015