我编写并行程序,它使用CAS操作来更新线程之间的共享内存。 (C ++,Linux,x86)
以下是我对'更新'的实施函数,对由变量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)。
如果从嵌套计算循环的最内部循环调用Update函数,带分支的 do ... while()循环将导致显着的分支误预测。但考虑到CAS的语义包括用于比较的分支,它可能是不可避免的。
在任何情况下,是否有上述更新功能的首选实现? 例如,在某些情况下,compare-exchange-strong可以击败compare-exchange-weak。如果Update函数中的函数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));
}
答案 0 :(得分:3)
标准库在<atomic>
atomic_compare_exchange_weak()
系列函数中具有可移植的实现。你可能会从中获得更好的表现。读取器线程可以使用轻松的内存顺序进行原子读取,如果它们只需要一些快照,或者如果需要最新的则获取。轻松的内存顺序可能与内存读取一样简单。
但是,大多数性能改进可能来自更好的无等待数据结构和算法。对于CAS来说,单链表往往是一个非常快速的等待结构。
有一些特殊情况。我相信你知道,如果只有一个线程是一个编写器,其他人可以简单地用获取/释放语义读取更新,甚至放松内存顺序。 (或者,作为gcc / clang扩展名,以匹配您使用的内置素,通过volatile*
。)
如果您经常看到其他线程完成并尝试同时更新,则可能有一种方法可以更改算法以将工作空间分开。在某些算法中,可能有一个原因让线程看到更新后退并屈服于其他人。
还要警惕A-B-A错误。您似乎没有检查它。如果您不需要,您可以一次利用cmpxch16b
指令对一个16字节结构进行CAS,并获得比单指针CAS更好的原子更新。