【发布时间】:2020-04-19 12:26:21
【问题描述】:
我最近在 32 核 Skylake 英特尔处理器上对 std::atomic::fetch_add 与 std::atomic::compare_exchange_strong 进行了基准测试。不出所料(从我听说过的有关 fetch_add 的神话中),fetch_add 的可扩展性几乎比 compare_exchange_strong 高一个数量级。看程序的反汇编std::atomic::fetch_add是用lock add实现的,std::atomic::compare_exchange_strong是用lock cmpxchg实现的(https://godbolt.org/z/qfo4an)。
是什么让lock add 在英特尔多核处理器上运行得如此之快?据我了解,两条指令的缓慢来自缓存线的争用,并且要以顺序一致性执行两条指令,执行的 CPU 必须以独占或修改模式将这条线拉入它自己的核心(来自MESI)。那么处理器如何在内部优化 fetch_add 呢?
This 是基准测试代码的简化版本。 compare_exchange_strong 基准测试没有 load+CAS 循环,只有 atomic 上的 compare_exchange_strong 输入变量不断随线程和迭代而变化。所以这只是多个 CPU 争用下指令吞吐量的比较。
【问题讨论】:
-
您应该展示您的基准测试代码,因为它通常是问题所在。请注意,即使没有使用 LOCK 前缀,您也会遇到相同的缓存争用问题,因为 CPU 必须将缓存线拉入内存以进行任何小于完整缓存线宽度的写入。
-
@RossRidge 使用基准测试代码编辑了问题。
-
你能用实际数字(iter/s)发布实际的循环反汇编吗?
-
你并没有真正对
fetch_add()进行基准测试。因为您没有使用获取的值,所以编译器将其优化为原子的“不获取,只需添加”,而您基准测试的正是这个“不获取,只需添加”。 -
您可以将锁定指令视为采用内存互斥锁:内存系统中可以被内存访问系统锁定的最小部分的互斥锁。它的工作方式保证不会发生死锁,并且锁不必是乐观的(它永远不必被取消)。
标签: c++ assembly x86 atomic cpu-architecture