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

时间:2018-11-11 18:10:43

标签: c++ performance synchronization x86-64 atomic

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

2 个答案:

答案 0 :(得分:4)

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

如果不进行优化就进行编译,则代码生成将是相似的,但是在启用常规优化的情况下进行编译可能会产生巨大差异。未优化的代码类似于使每个变量{{1} }。


当前的编译器也永远不会将volatile变量的多次读取合并为一个,就好像您曾经使用过atomic一样,因为这是人们的期望,并且关于如何允许变量的问题尚未解决有用的优化,同时禁止您不需要的优化。 (Why don't compilers merge redundant std::atomic writes?Can and does the compiler optimize out two atomic loads?)。

这不是一个很好的例子,但是可以想象一下,检查布尔值是在一个内联函数内部完成的,并且循环内还有其他内容。 (否则,您会像普通人一样将volatile atomic<T>放在循环中。)

if

See the asm output on Godbolt

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

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

(C ++的as-if规则将使编译器可以轻松地提升负载,因为它可以轻松地进行非原子访问。并且可以合并,因为每次读取相同的值是读取的全局顺序的一种可能结果一个值。但是正如我所说,编译器不会这样做。)


atomic_bool数组上的循环可以自动矢量化,但不能bool上循环。


此外,使用atomic<bool> []b ^= 1;之类的布尔值反转可以只是常规RMW,而不是原子RMW,因此它不必使用b++或{{1 }}。 (x86原子RMW只能通过顺序一致性与运行时重新排序才能实现,即lock xor前缀也是一个完整的内存屏障。)

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

lock btc

编译为将lock保留在寄存器中的asm。不幸的是,它并没有优化为零(之所以如此,可能是因为对布尔值进行偶次翻转会将其恢复为原始值)。但这可以使用更智能的编译器。

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

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

答案 1 :(得分:1)

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

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