[英]C/C++: relaxed std::atomic<bool> vs unlocked bool on X64 architecture
使用解锁的布尔值比使用std::atomic<bool>
的操作总是在宽松的内存顺序下完成有效率吗? 我认为两者最终都可以编译为同一机器代码,因为单个字节实际上在X64硬件上是原子的。 我错了吗?
是的,有潜在的巨大优势,特别是对于局部变量或在同一函数中重复使用的任何变量。 无法将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;
}
但是使用非原子bool
,编译器可以通过提升负载来进行转换,然后自动向量化简单的求和循环(或根本不运行它)。
使用atomic_bool
不能。 使用atomic_bool,asm循环与C ++源代码非常相似,实际上是在每次循环迭代内对变量的值进行测试和分支。 当然,这会挫败自动矢量化。
(C ++的假设规则将允许编译器提升负载,因为它很轻松,因此可以使用非原子访问进行重新排序。并且合并是因为每次读取相同的值是读取一个值的全局指令的一种可能结果。但是正如我所说,编译器不会这样做。)
bool
数组上的循环可以自动矢量化,但不能自动进行atomic<bool> []
循环。
同样,将类似b ^= 1;
的布尔值求反b ^= 1;
或b++
可以只是常规RMW,而不是原子RMW,因此它不必使用lock xor
或lock 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)
(单独的原子加载/存储),您仍然会在循环中获得存储/重载,从而创建了一个6循环的循环依赖链存储/重载(在具有5周期存储转发延迟的Intel CPU上),而不是通过寄存器的1周期dep链。
在Godbolt进行检查,加载常规的bool
和std::atomic<bool>
生成不同的代码,尽管不是因为同步问题。 相反,编译器(gcc)似乎不愿意假设std::atomic<bool>
保证为0或1。这很奇怪。
尽管生成的代码在细节上略有不同,但Clang所做的事情也一样。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.