【问题标题】:Why do GCC inserts mfence where Clang dont use it?为什么 GCC 在 Clang 不使用它的地方插入 mfence?
【发布时间】:2019-10-05 21:39:20
【问题描述】:

为什么 GCC 和 Clang 会为此代码生成如此不同的 asm(x86_64,-O3 -std=c++17)?

#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);
}

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

Clang 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

我怀疑这里的 mfence 有点矫枉过正,对吗?还是 Clang 生成的代码在某些情况下会导致错误?

【问题讨论】:

  • 神螺栓比较gcc.godbolt.org/z/GFCEY3
  • 看起来,由于 atomic 是一个局部变量,clang 认识到只有一个线程可以访问它并完全避免为 atomic 生成代码。
  • 所以GCC没有优化好mfence可以扔掉?
  • GCC 没有在核心语言级别获得原子,它们被视为库函数调用,想想printf,从未删除。 Clang 生成预期的代码。
  • 也许如果你能解释为什么你想要一个无意义的伪释放操作来产生一个栅栏,我们就可以解释为什么直觉是不正确的。向全世界发布你已经完成某事的拍摄,并设置了一面旗帜来说明这一点。你在向谁开枪,你在设置什么旗帜?

标签: c++ multithreading gcc clang atomic


【解决方案1】:

更真实的example

#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 前缀。 lockmfence 都满足std::memory_order_seq_cst 的要求,即无重排和全序。

来自 Intel 64 和 IA-32 架构软件开发人员手册:

MFENCE - 内存栅栏

对之前发出的所有从内存加载和存储到内存指令执行序列化操作 MFENCE 指令。这种序列化操作保证了之前的每条加载和存储指令 程序顺序中的 MFENCE 指令在随后的任何加载或存储指令之前变得全局可见 MFENCE 指令。 MFENCE 指令相对于所有加载和存储指令进行排序,其他 MFENCE 指令、任何 LFENCE 和 SFENCE 指令,以及任何序列化指令(例如 CPUID 操作说明)。 MFENCE 不序列化指令流。

8.2.3.8 锁定指令有总订单

内存排序模型确保所有处理器都同意所有锁定指令的单一执行顺序,包括大于 8 字节或未自然对齐的指令。

8.2.3.9 加载和存储不会使用锁定指令重新排序

内存排序模型防止加载和存储被执行的锁定指令重新排序 更早或更晚。

lock was benchmarked to be 2-3x faster than mfence 和 Linux 在可能的情况下从 mfence 切换到 lock

【讨论】:

  • gcc 的 bugzilla 中是否有关于将 mfence 替换为 lock 的条目?
猜你喜欢
  • 2013-05-11
  • 2014-09-14
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-22
相关资源
最近更新 更多