kpd*_*dev 6 c++ multithreading gcc atomic clang
为什么GCC和Clang会为此代码(x86_64,-O3 -std = c ++ 17)生成如此不同的asm?
#include <atomic>
int global_var = 0;
int foo_seq_cst(int a)
{
std::atomic<int> ia;
ia.store(global_var + a, std::memory_order_seq_cst);
return ia.load(std::memory_order_seq_cst);
}
int foo_relaxed(int a)
{
std::atomic<int> ia;
ia.store(global_var + a, std::memory_order_relaxed);
return ia.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
GCC 9.1:
foo_seq_cst(int):
add edi, DWORD PTR global_var[rip]
mov DWORD PTR [rsp-4], edi
mfence
mov eax, DWORD PTR [rsp-4]
ret
foo_relaxed(int):
add edi, DWORD PTR global_var[rip]
mov DWORD PTR [rsp-4], edi
mov eax, DWORD PTR [rsp-4]
ret
Run Code Online (Sandbox Code Playgroud)
铛8.0:
foo_seq_cst(int): # @foo_seq_cst(int)
mov eax, edi
add eax, dword ptr [rip + global_var]
ret
foo_relaxed(int): # @foo_relaxed(int)
mov eax, edi
add eax, dword ptr [rip + global_var]
ret
Run Code Online (Sandbox Code Playgroud)
我怀疑这里的mfence是一个过大的杀手,对吗?还是Clang生成的代码在某些情况下可能导致错误?
一个更现实的例子:
#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);
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
gcc使用mfence,而clang xchg用于std::memory_order_seq_cst。
xchg表示lock前缀。既lock和mfence满足的要求std::memory_order_seq_cst,这是没有重新排序和总次序。
摘自Intel 64和IA-32体系结构软件开发人员手册:
MFENCE-记忆栅栏
对在MFENCE指令之前发出的所有从内存加载和存储到内存指令执行序列化操作。此序列化操作可确保以程序顺序在MFENCE指令之前的每个加载和存储指令在MFENCE指令之后的任何加载或存储指令之前都变得全局可见。相对于所有加载和存储指令,其他MFENCE指令,任何LFENCE和SFENCE指令以及任何序列化指令(例如CPUID指令),MFENCE指令都进行了排序。MFENCE不会序列化指令流。
8.2.3.8 锁定指令具有总订单
内存排序模型可确保所有处理器都同意所有锁定指令的单个执行顺序,包括那些大于8字节或不自然对齐的指令。
8.2.3.9 加载和存储未按锁定说明重新排序
内存排序模型可防止使用较早或较晚执行的锁定指令对加载和存储进行重新排序。
lock基准测试速度比mfence Linux 快2-3倍,并且Linux从可能的地方切换mfence到了lock。