使用解锁的布尔值比使用std::atomic<bool>
来获得效率更高,在return
中,操作始终以宽松的存储顺序进行?我认为两者最终都可以编译为同一机器代码,因为单个字节实际上在X64硬件上是原子的。我错了吗?
答案 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进行检查,加载常规bool
和std::atomic<bool>
会生成不同的代码,尽管不是由于同步问题。相反,编译器(gcc)似乎不愿意假设std::atomic<bool>
保证为0或1。奇怪的是。
Clang做同样的事情,尽管生成的代码在细节上略有不同。