Безопасен ли mov + mfence для NUMA? - PullRequest
0 голосов
/ 12 февраля 2019

Я вижу, что g ++ генерирует простые mov для x.load() и mov + mfence для x.store(y).Рассмотрим классический пример:

#include<atomic>
#include<thread>
std::atomic<bool> x,y;
bool r1;
bool r2;
void go1(){
    x.store(true);
}
void go2(){
    y.store(true);
}
bool go3(){
    bool a=x.load();
    bool b=y.load();
    r1 = a && !b;
}
bool go4(){
    bool b=y.load();
    bool a=x.load();
    r2= b && !a;
}





int main() {
    std::thread t1(go1);
    std::thread t2(go2);
    std::thread t3(go3);
    std::thread t4(go4);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    return r1*2 + r2;
}

, в котором в соответствии с https://godbolt.org/z/APS4ZY go1 и go2 переведены в

go1():
        mov     BYTE PTR x[rip], 1
        mfence
        ret
go2():
        mov     BYTE PTR y[rip], 1
        mfence
        ret

Для этого примера я спрашиваю, возможно липотоки t3 и t4 расходятся во мнениях относительно порядка, в котором записи, выполняемые t1 и t2, «просачиваются» в их соответствующие представления памяти.В частности, рассмотрим архитектуру NUMA, в которой t3 оказывается «ближе» к t1, а t4 «ближе» к t2.Может ли случиться так, что буфер хранения t1 или t2 «преждевременно сбрасывается» даже до достижения значения mfence, и тогда t3 или t4 имеет шанс наблюдать запись раньше, чем планировалось?

1 Ответ

0 голосов
/ 12 февраля 2019

Да, это безопасно.Нет специального параметра компилятора, который вам нужно включить для NUMA-безопасного кода, потому что asm не должен быть другим.

NUMA даже не относится к этому;многоядерная система x86 с одним сокетом уже может переупорядочивать память настолько, насколько позволяет модель памяти x86.(Возможно, реже или с меньшими временными интервалами.)


TLDR.1: вы, похоже, неправильно понимаете, что делает mfence.Это локальный барьер для ядра, которое его запускало (включая StoreLoad, единственный переупорядочивающий x86 допускает без барьеров для загрузок / хранилищ не-NT).Это совершенно неважно, даже если x86 был слабо упорядочен: Мы рассматриваем по 1 хранилищу в каждом из разных ядер, поэтому порядок операций одного ядра относительно.друг друга не имеет значения.

(mfence просто заставляет это ядро ​​ждать выполнения каких-либо нагрузок, пока его хранилище не станет глобально видимым. Ничего особенного не происходит, когда хранилище фиксирует, пока mfence ожидает Гарантирует ли барьер памяти, что согласованность кэша завершена? .)


TL: DR.2: Будут ли две атомарные записи вдругие местоположения в разных потоках всегда будут рассматриваться в одном и том же порядке другими потоками? C ++ позволяет разным потокам не соглашаться с порядком хранения в расслабленных или освобожденных хранилищах (и, конечно, получать нагрузки, исключая переупорядочение LoadLoad),но не с seq_cst.

На архитектурах, где это возможно, компиляторам требуются дополнительные барьеры в последовательных хранилищах для предотвращения этого. На x86 это не возможно, полный останов. Любая x86-подобная система, которая допускала такое переупорядочение, на самом деле не будет x86 и не сможет правильно работатьвсе программное обеспечение x86.

Все основные системы x86, которые вы можете купить , на самом деле x86, с согласованным кэшем и подчиняются модели памяти x86.


x86's Модель памяти TSO требует, чтобы все ядра могли согласовать общий порядок хранения

Таким образом, соответствующее правило буквально соответствует названию модели памяти.

Свойство TSO следует непосредственно из каждого ядра, сохраняя свои собственные хранилища закрытыми до тех пор, пока они не передадут в L1d, и из-за наличия связных кэшей.

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

Единственный способ передачи данныхполучить доступ между ядрами можно, поместив кэш L1d, чтобы сделать его видимым глобально ;не делиться с некоторыми ядрами раньше других.(Это важно для TSO, независимо от NUMA).

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

Данные могут передаваться из буфера хранилища в частный L1d только тогда, когда это ядро ​​имеет соответствующую строку в состоянии Modified, что означает, что любое другое ядро ​​имеет ее в состоянии Invalid.Это (наряду с остальными правилами MESI) обеспечивает согласованность: никогда не может быть конфликтующих копий строки кэша в разных кэшах. Таким образом, после того, как хранилище передало в кеш, никакое другое ядро ​​не может загрузить устаревшее значение. ( Что будет использоваться для обмена данными между потоками, выполняющимися на одном Ядре с HT? )

Одним из распространенных заблуждений является то, что хранилища должны просачиваться через систему, прежде чем другие процессоры прекратят загрузку устаревших значений.Это на 100% неправильно в нормальных системах, которые используют MESI для поддержки связных кэшей. Кажется, вы тоже страдаете от этого заблуждения, когда говорите о том, что t3 "ближе" к t1. Это может быть верно для устройств DMA, если у вас некогерентный DMA, именно потому, что эти DMA считываютне будет согласован с представлением памяти, совместно используемой процессорами, участвующими в протоколе MESI.(Но современный x86 также имеет DMA с когерентным кэшем.)

На самом деле, нарушение TSO требует довольно забавного поведения, когда хранилище становится видимым для некоторых других ядер, прежде чем становится видимым для всех. PowerPC делает это в реальной жизни для логических потоков на одном физическом ядре, отслеживающих удаленных хранилищ друг друга, которые еще не зафиксировали в кэш L1d.См. Мой ответ на Будут ли две атомные записи в разные места в разных потоках всегда рассматриваться в одном и том же порядке другими потоками? Это редко, даже на слабо упорядоченных ISA, которые позволяют это на бумаге.


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

(я не уверен, существуют ли такие звери).

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

Как говорит Википедия , по сути, все системы NUMA являются NUMA, связанными с кешем, он же ccNUMA.

Хотя системы NUMA, не связанные с кэшем, проще в проектировании, они становятся слишком сложными для программирования в стандартном фоновом режиме.Модель программирования архитектуры Neumann

Любая некогерентная система с общей памятью, использующая процессоры x86, не будет работать с одним экземпляром ядра в разных областях когерентности.Вероятно, он будет иметь пользовательскую библиотеку MPI и / или другие пользовательские библиотеки для использования общей памяти с явными сбросами / согласованностью для обмена данными между доменами (системами) когерентности.

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

Система с одной или несколькими картами Xeon Phi PCIe рассматривает каждый ускоритель Xeon Phi как отдельный«система», потому что они не связаны с основной памятью или друг с другом, только внутренне связаны .См. Нижний раздел ответа @ Hadi на Как кэши данных маршрутизируют объект в этом примере? .Вы можете перенести некоторую работу на ускоритель Xeon Phi, подобно тому, как вы бы разгрузили работу на GPU, но это делается с помощью чего-то вроде передачи сообщений.Вы бы не имели бы некоторые потоки, работающие на главном процессоре Skylake (например) и другие обычные потоки того же процесса, работающие на ядрах KNL на Xeon Phi.Если бы на плате Xeon Phi работала ОС, это был бы отдельный экземпляр Linux или любой другой из того, что работает на хост-системе.


x86 NUMA-системы реализуют MESI путем отслеживаниядругие сокеты перед загрузкой из локальной DRAM, чтобы поддерживать когерентность кэша.

И, конечно, запросы RFO (чтение для владения) передаются на другие сокеты.

Новые поколенияXeon ввел все больше и больше настроек Snoop, чтобы компенсировать различные аспекты производительности.(Например, более агрессивное отслеживание требует большей пропускной способности канала между сокетами, но может уменьшить задержку между ядрами между сокетами.)

Микросхемы, которые могут работать в четырехъядерных сокетах и ​​более крупных системах (E7 v1..4), имеют фильтры snoop;E5 v1.4 с двумя сокетами просто транслирует отслеживание на другой сокет, используя приличную долю пропускной способности QPI по сравнению с тем, что я прочитал.(Это относится к Xeons до Skylake-X, Broadwell и более ранним. SKX использует ячеистую сеть на чипе и может всегда иметь какую-то snoop-фильтрацию между сокетами. Я не уверен, что это делает. BDW и ранее использовали инклюзивныйКэш-память L3 в качестве snoop-фильтра для локальных ядер, но SKX имеет не включающий L3 и, следовательно, нуждается в чем-то еще для snoop-фильтрации даже в пределах одного сокета.

Многопроцессорные чипы AMD, используемые для использования Hypertransport. Zen использует InfinityМатрица между кластерами из 4 ядер в одном сокете; я предполагаю, что он также использует это между сокетами.

(Забавный факт: гипертранспорт с несколькими сокетами AMD K10 Opteron может вызвать разрыв на границах 8 байтов, в то время как в пределах одногоЗагрузка / хранение 16-байтовых SIMD-сокетов на практике были атомарными. Инструкции SSE: какие процессоры могут выполнять атомные операции с памятью 16B? и Атомность в x86 . Если считать это переупорядочением, этоодин случай, когда мульти-сокет может сделать больше странностей памяти, чем один сокет. Но это не зависит отNUMA per se;у вас будет то же самое со всей памятью, подключенной к одному сокету для настройки UMA.)


Related:

См. также повторяющиеся ссылки в В чем разница в логике и производительности между LOCK XCHG и MOV + MFENCE? для xchg против mov + mfence.На современных процессорах, особенно Skylake, mov + mfence определенно медленнее для некоторых способов тестирования, чем xchg, и оба являются эквивалентными способами создания seq_cst хранилища.

A releaseили relaxed хранилищу просто необходим простой mov, и он все еще имеет те же гарантии заказа TSO.

Я думаю, что даже слабо упорядоченные хранилища NT все еще видны всеми ядрами в порядке, с которым они могут согласиться.«Слабость» - это порядок, который становится глобально видимым по отношению к.другие нагрузки + хранилища из ядра делают их.

...