C/C++:放松 std::atomic<bool> 与 X64 架构上的解锁 bool

C/C++: relaxed std::atomic<bool> vs unlocked bool on X64 architecture

与使用 std::atomic<bool> 相比,使用解锁的布尔值是否有任何效率优势,其中操作总是以宽松的内存顺序完成?我假设两者最终都编译为相同的机器代码,因为单个字节在 X64 硬件上实际上是原子的。我错了吗?

Godbolt 检查,加载常规 boolstd::atomic<bool> 生成不同的代码,尽管不是因为同步问题。相反,编译器 (gcc) 似乎不愿意假设 std::atomic<bool> 保证为 0 或 1。奇怪,那个。

Clang 做同样的事情,尽管生成的代码在细节上略有不同。

是的,有潜在的巨大优势,特别是对于局部变量,或在同一函数中重复使用的任何变量。 无法将atomic<>变量优化为寄存器。

如果你在没有优化的情况下编译,代码生成会很相似,但是在启用正常优化的情况下编译可能会有很大的不同。未优化的代码类似于制作每个变量 volatile.


当前的编译器也永远不会将对 atomic 变量的多次读取合并为一个,就好像您使用了 volatile atomic<T> 一样,因为这是人们所期望的,而且尘埃尚未落定允许进行有用的优化,同时禁止您 想要的优化。 ( and ).

这不是一个很好的例子,但想象一下,检查布尔值是在内联函数中完成的,并且循环中还有其他东西。 (否则你会像正常人一样将 if 放在循环中。)

int sumarr_atomic(int arr[]) {
    int sum = 0;
    for(int i=0 ; i<10000 ; i++) {
        if (atomic_bool.load (std::memory_order_relaxed)) {
            sum += arr[i];
        }
    }
    return sum;
}

See the asm output on Godbolt.

但是对于非原子 bool,编译器可以通过提升负载为您进行转换,然后自动矢量化简单的求和循环(或者根本不 运行 ).

对于 atomic_bool,它不能。使用 atomic_bool,asm 循环很像 C++ 源代码,实际上在每次循环迭代中对变量的值进行测试和分支。这当然会打败自动矢量化。

(C++ as-if 规则将允许编译器提升负载,因为它是放松的,因此它可以使用非原子访问重新排序。并合并,因为每次读取相同的值是全局顺序的一个可能结果读取一个值。但正如我所说,编译器不会那样做。)


遍历 bool 的数组可以自动矢量化,但不能遍历 atomic<bool> [].


此外,用 b ^= 1;b++ 之类的东西反转布尔值可以只是一个普通的 RMW,而不是原子 RMW,所以它不必使用 lock xorlock btc。 (x86 原子 RMW 仅适用于顺序一致性与 运行 时间重新排序,即 lock 前缀也是一个完整的内存屏障。)

修改非原子布尔值的代码可以优化实际修改,例如

void loop() {
    for(int i=0 ; i<10000 ; i++) {
        regular_bool ^= 1;
    }
}

编译为将 regular_bool 保存在寄存器中的 asm。不幸的是,它并没有优化到什么都没有(这可能是因为将布尔值翻转偶数次会将其设置回其原始值)。但它可以使用更智能的编译器。

loop():
    movzx   edx, BYTE PTR regular_bool[rip]   # load into a register
    mov     eax, 10000
.L17:                     # do {
    xor     edx, 1          # flip the boolean
    sub     eax, 1
    jne     .L17          # } while(--i);
    mov     BYTE PTR regular_bool[rip], dl    # store back the result
    ret

即使写成atomic_b.store( !atomic_b.load(mo_relaxed), mo_relaxed)(单独的原子loads/stores),你仍然会在循环中得到一个store/reload,通过创建一个6周期循环携带的依赖链store/reload(在具有 5 周期存储转发延迟的 Intel CPU 上)而不是通过寄存器的 1 周期 dep 链。