Более реалистичный пример :
#include <atomic>
std::atomic<int> a;
void foo_seq_cst(int b) {
a = b;
}
void foo_relaxed(int b) {
a.store(b, std::memory_order_relaxed);
}
gcc-9.1:
foo_seq_cst(int):
mov DWORD PTR a[rip], edi
mfence
ret
foo_relaxed(int):
mov DWORD PTR a[rip], edi
ret
clang-8.0:
foo_seq_cst(int): # @foo_seq_cst(int)
xchg dword ptr [rip + a], edi
ret
foo_relaxed(int): # @foo_relaxed(int)
mov dword ptr [rip + a], edi
ret
gccиспользует mfence
, тогда как clang использует xchg
для std::memory_order_seq_cst
.
xchg
подразумевает префикс lock
.И lock
, и mfence
удовлетворяют требованиям std::memory_order_seq_cst
, что означает отсутствие переупорядочения и общего порядка.
Из Руководства разработчика программного обеспечения для архитектуры Intel 64 и IA-32:
MFENCE - Memory Fence
Выполняет сериализацию всех инструкций загрузки из памяти и сохранения в память, которые были выполнены до инструкции MFENCE.Эта операция сериализации гарантирует, что каждая инструкция загрузки и сохранения, которая предшествует инструкции MFENCE в программном порядке, становится видимой глобально перед любой инструкцией загрузки или сохранения, которая следует за инструкцией MFENCE.Инструкция MFENCE упорядочена в отношении всех инструкций загрузки и хранения, других инструкций MFENCE, любых инструкций LFENCE и SFENCE и любых инструкций сериализации (таких как инструкция CPUID).MFENCE не сериализует поток команд.
8.2.3.8 Заблокированные инструкции имеют общий порядок
Модель упорядочения памяти гарантирует, что все процессоры согласуются на один порядок выполнениявсех заблокированных инструкций, включая те, которые больше 8 байтов или не выровнены естественным образом.
8.2.3.9 Грузы и хранилища не переупорядочиваются с заблокированными инструкциями
Модель упорядочения памяти предотвращает переупорядочение нагрузок и хранилищ с заблокированными инструкциями, которые выполняются раньше или позже.
lock
было оценено в 2-3 раза быстрее, чем mfence
, и Linux переключился с mfence
на lock
, где это возможно.