c++ - C/C++:轻松的 std::atomicvs X64 架构上的未锁定布尔值
问题描述
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;
}
但是对于 non-atomic bool
,编译器可以通过提升负载为您进行转换,然后自动矢量化简单的 sum 循环(或根本不运行它)。
,atomic_bool
它不能。使用 atomic_bool,asm 循环很像 C++ 源代码,实际上在每次循环迭代中对变量的值进行测试和分支。这当然会破坏自动矢量化。
(C++ as-if 规则将允许编译器提升负载,因为它是放松的,因此它可以使用非原子访问重新排序。并且合并是因为每次读取相同的值是读取一个值的全局顺序的一种可能结果。但正如我所说,编译器不会那样做。)
bool
循环遍历can 自动矢量化的数组,但不能遍历atomic<bool> []
.
此外,用类似的东西反转布尔值b ^= 1;
orb++
可以只是一个常规 RMW,而不是原子 RMW,所以它不必使用lock xor
or 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 链。
推荐阅读
- java - JAVA - 找出距离事件多长时间
- ruby-on-rails - Rails:嵌套表单不创建记录
- oracle - 使用 rdsadmin.rds_file_util.listdir 创建 Oracle 过程
- c++ - 带有 numeric_limits 的 SFINAE
::max() 在 MSVC2017 上 - scheme - apply、apply-primitive-procedure 和 apply-in-underlying-scheme
- r - 更改格式并替换管道中的零
- python - 熊猫数据框,如何按索引选择 N 个周围点
- c++ - C++11 标准是否保证零值有符号整数的一元减号为零?
- java - 如何使浮动按钮消失android?
- c# - 是否有一种简单的方法可以在 System.Text.Json 的自定义转换器中手动序列化/反序列化子对象?