C / C ++:轻松的std :: atomic <bool>与X64体系结构上的解锁bool

toh*_*ava 1 c++ performance synchronization x86-64 atomic

使用解锁的布尔值比使用std::atomic<bool>始终以宽松的内存顺序完成操作的方法有效率吗?我认为两者最终都可以编译为同一机器代码,因为单个字节实际上在X64硬件上是原子的。我错了吗?

Pet*_*des 5

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

如果您不进行优化而进行编译,则代码生成将是相似的,但是在启用常规优化的情况下进行编译可能会有巨大的差异。 未优化的代码类似于制作每个变量volatile


目前的编译器还从来没有一个组合的多次读取atomic可变进一个,因为如果你使用的volatile atomic<T>,因为这是人们所期待和灰尘也没有对如何让有用的优化,同时禁止你的人还没有解决想要的。(为什么编译器不合并多余的std :: atomic写?以及编译器是否可以优化两个原子负载?)。

这不是一个很好的例子,但是可以想象一下,检查布尔值是在一个内联函数内完成的,并且循环内还有其他内容。(否则,您将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;
}
Run Code Online (Sandbox Code Playgroud)

请参阅Godbolt上的asm输出

但是使用非原子bool的编译器可以通过提升负载来进行转换,然后自动向量化简单的求和循环(或根本不运行它)。

使用atomic_bool,它不能。使用atomic_bool,asm循环与C ++源代码非常相似,实际上是在每次循环迭代内对变量的值进行测试和分支。当然,这会挫败自动矢量化。

(C ++的假设规则将允许编译器提升负载,因为它很轻松,因此可以通过非原子访问进行重新排序。并且合并是因为每次读取相同的值是读取一个值的全局命令的一种可能结果。但是正如我所说,编译器不会这样做。)


循环可以遍历boolcan 的数组,但不会结束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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译为保存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
Run Code Online (Sandbox Code Playgroud)

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