哪种自旋锁方法更有效:重试 test_and_set(),还是在 test() 上自旋只读?

问题描述 投票:0回答:1

哪种自旋锁方法更好(在效率方面)?

#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 );
}

这个例子来自cppreference。当我们在外循环中添加/删除对

test(std::memory_order_relaxed)
的调用时会发生什么?

我看到两种方法之间生成的代码有显着差异(here)。

c++ performance c++20 atomic spinlock
1个回答
2
投票

一般情况下,在

.test()
上旋转只读版本是最好的,而不是从试图解锁它的线程中窃取缓存行的所有权。特别是如果自旋锁与任何其他数据位于相同的缓存行中,比如锁所有者可能正在读取的数据,你就会以这种方式创建更多更糟糕的错误共享。

此外,如果多个线程在同一个自旋锁上自旋等待,您不希望它们在内核之间的互连上浪费带宽,ping-ponging 包含锁的缓存行。 (如果经常发生多线程自旋,那么纯自旋锁通常是一个糟糕的选择。通常您希望最终通过操作系统辅助睡眠/唤醒将 CPU 交给另一个线程,例如通过

futex
。C++20
.wait()
.notify_one()
可以做到这一点,或者只是使用
std::mutex
std::shared_mutex
的良好实现。)。

详情见:


不幸的是,C++ 缺少像 Rust 的

core::hint::spin_loop
这样的可移植函数,它将在 x86 上编译为
pause
指令,或在其他 ISA 上等效。

因此,只读循环将在具有超线程的 CPU 上浪费更多的执行资源(从其他逻辑核心窃取它们),但如果其他任何东西正在读取该行,则浪费更少的存储缓冲区条目和更少的核外流量。特别是如果你有多个线程自旋等待同一个锁,ping-ponging 缓存行!

如果你不介意

#ifdef __amd64__
/
#include <immintrin.h>
for
_mm_pause()
,那么你也可以拥有这个优势。

© www.soinside.com 2019 - 2024. All rights reserved.