相对于无争议的原子变量(例如 C++ 的 std::atomic<T>
操作),它的速度快/慢多少。
此外,有争议的原子变量相对于无争议的锁会慢多少?
我正在使用的架构是 x86-64。
相对于无争议的原子变量(例如 C++ 的 std::atomic<T>
操作),它的速度快/慢多少。
此外,有争议的原子变量相对于无争议的锁会慢多少?
我正在使用的架构是 x86-64。
我碰巧有很多低级速度测试结果。然而,速度的确切含义是非常不确定的,因为它在很大程度上取决于您正在做什么(即使与操作本身无关)。
以下是一些来自 AMD 64 位 Phenom II X6 3.2Ghz 的数字。我也在 Intel 芯片上运行过此测试,时间差异很大(同样取决于具体要执行的操作)。
使用 GCC 的 __sync_fetch_and_add
,这将是一个完全同步的原子加法,平均需要 16ns,最短时间为 4ns。最短时间可能更接近真实值(但那里也有一些开销)。
一个未被争用的 pthread 互斥量(通过 boost 实现)需要 14ns(这也是其最短时间)。请注意,这也有点太短了,因为如果其他东西锁定了互斥量但现在没有争用(因为它会导致缓存同步),时间实际上会增加。
一个失败的 try_lock 需要 9ns。
我没有普通的原子递增,因为在 x86_64 上,这只是一个普通的交换操作。可能接近最小可能时间,所以是 1-2ns。
在条件变量上没有等待者的情况下调用 notify 需要 25ns(如果有大约 304ns 的等待线程)。
然而,由于所有锁定都会导致某些 CPU 引导保证,所以您修改的内存量(适合存储缓冲区的任何内容)将改变这些操作所需的时间。显然,如果互斥量存在争用,那么这将是最差的情况。即使没有实际发生线程切换,任何返回到 Linux 内核的操作也可能需要数百纳秒。这通常是原子锁表现优异的地方,因为它们不涉及任何内核调用:您的平均情况性能也是最差情况的性能。如果有等待线程,则互斥解锁也会产生开销,而原子解锁则不会。
注意:进行此类测量存在很多问题,因此结果总是受到质疑。我的测试尝试通过固定 CPU 速度、为线程设置 cpu 亲和性、不运行其他进程并对大型结果集进行平均来尽可能减少变化。
xchg
不可能进行原子递增操作(即使它有隐式的lock
前缀)。在大多数CPU上,lock add [mem], 1
的成本几乎与lock xadd [mem], eax
一样高,只是稍微简单了一点。它肯定不会像1ns(3GHz CPU上的3个时钟周期)那么快,lock
前缀的完整屏障不能阻止非内存指令的乱序执行。Agner Fog的指令表中没有K10的lock
数字,但Piledriver lock add
每约40个周期执行一次(与xchg [mem],reg
相同),而lock xadd
每约39个周期执行一次。 - Peter Cordes有一个GitHub上的项目,旨在测量不同平台上的这种情况。不幸的是,在我的硕士论文之后,我从未真正有时间跟进,但至少基本代码已经存在。
它测量pthread和OpenMP锁,与__sync_fetch_and_add
内部函数进行比较。
据我记得,我们预计锁和原子操作之间会有很大的差异(约为一个数量级),但实际差异非常小。
然而,现在在我的系统上进行测量得出的结果反映了我的最初猜测,即无论使用pthread还是OpenMP,原子操作都快约五倍,单个锁定增量操作需要约35纳秒(这包括获取锁,执行增量以及释放锁)。
lock add [mem], 1
都非常快。但是,无论如何,这很难进行微基准测试,因为在某些 ISA 上,弱排序的原子增量(例如 std::memory_order_relaxed)避免了内存屏障,其成本取决于正在等待的其他负载/存储的数量和无法重新排序的数量。 - Peter Cordes