dig*_*evo 5 c++ performance atomic spinlock c++20
哪种自旋锁方法更好(就效率而言)?
#include <atomic>
#define METHOD 1
int main( )
{
std::atomic_flag lock { };
#if METHOD == 1
while ( lock.test_and_set( std::memory_order_acquire ) )
{
while ( lock.test( std::memory_order_relaxed ) );
}
#else
while ( lock.test_and_set( std::memory_order_acquire ) );
#endif
lock.clear( std::memory_order_release );
}
Run Code Online (Sandbox Code Playgroud)
这个例子来自cppreference。test(std::memory_order_relaxed)当我们添加/删除对外部循环内部的调用时会发生什么?
我发现这两种方法(此处)生成的代码存在明显差异。
通常,以只读方式旋转的版本.test()是最好的,而不是从尝试解锁缓存行的线程中窃取缓存行的所有权。特别是如果自旋锁与任何其他数据(例如锁所有者可能刚刚读取的数据)位于同一缓存行中,那么您就会以这种方式创建更多、更糟糕的错误共享。
另外,如果多个线程在同一个自旋锁上等待自旋,您不希望它们在核心之间的互连上浪费带宽来对包含锁的缓存行进行乒乓操作。(如果经常发生多线程旋转,则纯自旋锁通常是一个糟糕的选择。通常您希望最终通过操作系统辅助的睡眠/唤醒(例如通过futex.C++20 )将 CPU 交给另一个线程,.wait()并且.notify_one()可以这样做这个,或者只是使用std::mutexor的良好实现std::shared_mutex。)。
更多详情请参见:
不幸的是,C++ 缺乏像 Rust 那样的可移植函数core::hint::spin_loop,该函数可以编译为pausex86 上的指令或其他 ISA 上的等效指令。
因此,只读循环将在具有超线程的 CPU 上浪费更多的执行资源(从其他逻辑核心窃取它们),但浪费更少的存储缓冲区条目和更少的核外流量(如果有其他东西正在读取该行)。特别是如果您有多个线程在同一个锁上自旋等待,从而对缓存行进行乒乓操作!
如果您不介意#ifdef __amd64__/ #include <immintrin.h>for _mm_pause(),那么您也可以拥有这个优势。