барьер памяти x86 mfence и C ++ - PullRequest
4 голосов
/ 19 марта 2019

Я проверяю, как компилятор выдает инструкции для многоядерных барьеров памяти на x86_64. Ниже приведен код, который я тестирую, используя gcc_x86_64_8.3.

std::atomic<bool> flag {false};
int any_value {0};

void set()
{
  any_value = 10;
  flag.store(true, std::memory_order_release);
}

void get()
{
  while (!flag.load(std::memory_order_acquire));
  assert(any_value == 10);
}

int main()
{
  std::thread a {set};
  get();
  a.join();
}

Когда я использую std::memory_order_seq_cst, я вижу, что инструкция MFENCE используется с любой оптимизацией -O1, -O2, -O3. Эта инструкция гарантирует, что буферы хранилища сброшены, поэтому обновляют свои данные в кеше L1D (и используют протокол MESI, чтобы убедиться, что другие потоки могут видеть эффект).

Однако, когда я использую std::memory_order_release/acquire без оптимизаций, MFENCE инструкция также используется, но инструкция опускается с использованием -O1, -O2, -O3 оптимизаций и не видит других инструкций, которые очищают буферы.

В случае, когда MFENCE не используется, что гарантирует, что данные буфера хранения сохраняются в кэш-памяти для обеспечения семантики порядка памяти?

Ниже приведен код сборки для функций get / set с -O3, например, что мы получаем в проводнике компилятора Godbolt :

set():
        mov     DWORD PTR any_value[rip], 10
        mov     BYTE PTR flag[rip], 1
        ret


.LC0:
        .string "/tmp/compiler-explorer-compiler119218-62-hw8j86.n2ft/example.cpp"
.LC1:
        .string "any_value == 10"

get():
.L8:
        movzx   eax, BYTE PTR flag[rip]
        test    al, al
        je      .L8
        cmp     DWORD PTR any_value[rip], 10
        jne     .L15
        ret
.L15:
        push    rax
        mov     ecx, OFFSET FLAT:get()::__PRETTY_FUNCTION__
        mov     edx, 17
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:.LC1
        call    __assert_fail

Ответы [ 2 ]

7 голосов
/ 19 марта 2019

Модель упорядочения памяти x86 обеспечивает барьеры #StoreStore и #LoadStore для всех инструкций хранилища 1 , и это все, что требуется в семантике выпуска.Также процессор выполнит инструкцию сохранения как можно скорее;когда инструкция сохранения удаляется, хранилище становится самым старым в буфере хранения, ядро ​​имеет целевую строку кэша в состоянии когерентности, доступной для записи, и порт кэша доступен для выполнения операции сохранения 2 .Таким образом, нет необходимости в инструкции MFENCE.Флаг станет видимым для другого потока, как только это станет возможным, и когда он это сделает, any_value гарантированно будет равно 10.

С другой стороны, последовательная согласованность также требует барьеров #StoreLoad и #LoadLoad.MFENCE требуется для обеспечения обоих барьеров 3 , поэтому он используется на всех уровнях оптимизации.

В отношении: Размер буферов хранилища на оборудовании Intel?Что такое буфер хранилища? .


Сноски:

(1) Существуют исключения, которые здесь не применяются.В частности, временные хранилища и хранилища в не кэшируемых типах памяти, сочетающих запись, обеспечивают только барьер #LoadStore.В любом случае, эти барьеры предусмотрены для хранилищ с типом памяти с обратной записью на процессорах Intel и AMD.

(2) В отличие от хранилищ с комбинированием записи, которые становятся видимыми глобально при определенных условиях.См. Раздел 11.3.1 руководства Intel, том 3.

(3) См. Обсуждение под ответом Питера.

6 голосов
/ 19 марта 2019

Модель памяти TSO x86 является последовательной последовательностью + буфер хранилища, поэтому только хранилища seq-cst нуждаются в специальном ограждении. (остановка после хранилища, пока буфер хранилища не опустошится, до последующих загрузок - всенам нужно восстановить последовательную последовательность).Более слабая модель acq / rel совместима с переупорядочением StoreLoad, вызванным буфером хранилища.

(См. Обсуждение в комментариях относительно того, является ли «разрешение переупорядочения StoreLoad» точным и достаточным описанием того, что разрешает x86. Ядровсегда видит свои собственные хранилища в программном порядке, потому что загружает отслеживание буфера хранилища, так что вы могли бы сказать, что пересылка хранилища также переупорядочивает загрузки недавно сохраненных данных. За исключением того, что вы не можете всегда: Глобально невидимые инструкции загрузки )

(И, кстати, компиляторы, отличные от gcc, используют xchg для создания хранилища seq-cst. На самом деле это больше эффективно для текущих процессоров. mov + mfence GCC можетраньше они были дешевле, но в настоящее время обычно хуже, даже если вас не волнует старое значение. См. Почему хранилище std :: atomic с последовательной последовательностью использует XCHG? для сравнения между GCCmov+mfence против xchg. Также мой ответ на Какой лучший барьер для записи на x86: lock + addl или xchgl? )

FФакт: вы можете добиться последовательной согласованности, вместо этого ограждая seq-cst , загружает вместо хранилищ.Но в большинстве случаев дешевые грузы гораздо ценнее, чем дешевые магазины, поэтому каждый использует ABI, где в магазинах находятся полные барьеры.

См. https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html для получения подробной информации о том, как C ++ 11атомарные операции сопоставляются с последовательностями команд asm для x86, PowerPC, ARMv7, ARMv8 и Itanium.Также Когда требуются инструкции x86 LFENCE, SFENCE и MFENCE?


, когда я использую std :: memory_order_release / acqu без оптимизации, также используется инструкция MFENCE

Это потому, что flag.store(true, std::memory_order_release); не встроен, , потому что вы отключили оптимизацию.Это включает в себя встраивание очень простых функций-членов, таких как `` atomic :: store (T, std :: memory_order = std :: memory_order_seq_cst) `

Когда параметр упорядочения для встроенной команды __atomic_store_n() GCC равенпеременная времени выполнения (в реализации библиотеки atomic::store()), GCC воспроизводит ее консервативно и переводит ее в seq_cst. Это может стоить того, чтобы gcc перешагнул через mfence, потому что это так дорого, но это не то, что мы получаем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...