在C++中,用于原子更新的 CAS 最有效的实施是什么?
What's the most efficient implementation of CAS for atomic updates in C++?
我编写并行程序,该程序使用 CAS 操作来更新线程之间的共享内存。(C++, Linux, x86)
以下是我对"Update"函数的实现,该函数对变量 a 指向的内存位置应用更新,该位置由 f(*a, b) 返回的值。
inline bool CAS (int64_t* ptr, int64_t old_val, int64_t new_val) {
return __sync_bool_compare_and_swap(ptr, old_val, new_val);
}
inline void Update(int64_t* a, int64_t& b) {
volatile int64_t expected, result;
do {
expected = *a;
result = f(expected, b);
} while (!CAS(a, expected, result));
}
我看到大多数其他实现使用几乎相同的代码。
但我只是想知道它是否是最有效的,因为我从Vtune剖面器中看到相当高的CPI率(1.2~1.5)。
如果从嵌套计算循环的最内部循环调用更新函数,则执行...而带有分支的 ()循环会导致严重的分支错误预测。但考虑到 CAS 的语义包括用于比较的分支,这可能是不可避免的。
在任何情况下,上述更新功能是否有任何首选实现? 例如,在某些情况下,比较交换强可以击败比较交换弱。如果更新函数中的函数 f 用于加法,则最好使用 std::atomic 提供的atomic_fetch_and_add。
这是带有注释的更新代码(没有观察到性能提升,我正在微优化。但无论如何,在最坏的情况下可能会更好)
inline bool CAS (int64_t* ptr, int64_t& old_val, int64_t new_val) {
return (std::atomic_compare_exchange_weak((std::atomic<int64_t>*) ptr, &old_val, new_val);
}
inline void Update(int64_t* a, int64_t& b) {
int64_t expected, result;
do {
expected = *a;
result = f(expected, b);
} while (!CAS(a, expected, result));
}
标准库在<atomic>
中有一个可移植的实现,atomic_compare_exchange_weak()
函数族。 您可能会从中获得更好的性能。 如果读取器线程只需要一些快照,则可以使用宽松的内存顺序执行原子读取,或者在需要最新快照时获取。 宽松的内存顺序可能与读取内存一样简单。
但是,大多数性能改进可能来自更好的免等待数据结构和算法。 对于 CAS 来说,单向链表通常是一种非常快速的免等待结构。
有一些特殊情况。我相信您知道,如果只有一个线程是编写器,则其他线程可以简单地使用获取/发布语义甚至宽松的内存顺序读取更新。(或者,作为 gcc/clang 扩展以匹配您使用的内置,通过volatile*
。
如果您经常看到其他线程完成并尝试同时更新,则可能有一种方法可以更改算法以将工作线程隔开。 在某些算法中,看到更新的线程可能会退缩并让位于其他线程。
还要警惕 A-B-A 错误。 您似乎没有检查它。 如果不需要,则可以利用cmpxch16b
指令一次对 16 字节结构进行 CAS 处理,并获得比在单个指针上使用 CAS 更好的原子更新。
- 欧拉项目#8答案是大以获得有效答案
- 调整大小后指向元素值的指针unordered_map有效?
- 为什么是0;C++中的有效语句
- 最高有效数字侧的第N位
- GCC对可能有效的代码抛出init list生存期警告
- 有效地使用std::unordered_map来插入或增加键的值
- c++中O(n^(1/3))中一个数的除数的有效计数
- 使用无符号字符数组有效存储内存
- 自定义先决条件对移动分配运算符有效吗
- 为什么将值返回函数传递给重载=运算符对运算符函数有效,而对其他运算符无效
- 有哪些有效的方法可以消除一组 100 万个字符串>重复数据?
- 为什么这种直接初始化有效?(C++17)
- 递归函数有效,但无法记忆
- 在C++中初始化向量映射的最有效方法
- 如果变量名称不跟在 char* 后面,const char* 是否有效?
- 钳制迭代器是否有效
- 如何有效地在 std::vector 中插入一对?
- C++模板 t 不是有效的模板类型
- 检查由括号、方括号和大括号组成的一组方括号是否有效?
- 在C++中,用于原子更新的 CAS 最有效的实施是什么?