【问题标题】:Why second spin in Spinlock gives performance boost?为什么 Spinlock 中的第二次旋转会提高性能?
【发布时间】:2022-01-27 23:59:37
【问题描述】:

这是一个使用std::atomic_flag 实现的基本自旋锁。
book 的作者声称,在 lock() 中的第二个可以提高性能。

class Spinlock
{
    std::atomic_flag flag{};
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            while (flag.test(std::memory_order_acquire)); //Spin here
        }
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

我们在一个额外的内部循环中使用test() 的原因是性能: test() 不会使缓存行无效,而 test_and_set() 会。

有人可以详细说明这句话吗? test 还是读操作,需要从内存中读取吧?

【问题讨论】:

    标签: c++ performance atomic


    【解决方案1】:

    读取内存地址不会清除缓存行。

    写作确实如此。

    所以在现代计算机中,有 RAM,并且“围绕”CPU 有多层缓存(它们被称为 L1、L2 和 L3 缓存,但重要的是它们是层,而 CPU 是在中间)。在多核系统中,通常外层是共享的;最内层通常不是,而是特定于给定 CPU。

    清除缓存行意味着通知所有其他持有此内存的缓存“您拥有的数据可能已过时,请将其丢弃”。

    Test 和 set 写入 true 并自动返回旧值。它会清除缓存行,因为它会写入。

    测试不写。如果您有另一个线程与该线程不同步,则不必戳它读取此内存的缓存。

    外部循环写入true,如果替换为false则退出。内循环等待直到有一个假可见,然后落到外循环。内部循环不需要清除所有其他 cpu 的原子标志值的缓存状态,但外部循环必须清除(因为它可以将 false 更改为 true)。由于旋转可能会持续一段时间,因此避免连续清除缓存似乎是个好主意。

    【讨论】:

    • 我认为发帖者对 CPU 内存缓存更加困惑(即读取可能实际上不会访问 RAM)
    • @CollinDauphinee 添加了一段关于 CPU 内存缓存的内容。
    • 需要说明低级组合的testAndSet机器指令,如果不做set对cache line做了什么。
    【解决方案2】:

    (原子)存储需要独占访问缓存行,但普通原子(释放)存储仍然相对有效,因为涉及存储缓冲区“缓存”; IE。不需要立即访问缓存行。

    在自旋锁示例中,存储 (test_and_set) 是原子读-修改-写 (RMW) 操作,由于其性质,该操作显着变慢;它在一个操作中读取和写入。
    这需要立即访问高速缓存行,而在存储缓冲区中仍待处理的任何内容都必须刷新才能继续。 此外,高速缓存行必须被锁定以确保读写之间的独占访问。 这很昂贵,但自旋锁需要保证独占锁访问。

    当多个线程在同一个test_and_set 操作上执行时,缓存行会在 CPU 内核之间不断跳动。
    内部循环(使用原子读取)是一种优化,因为多个线程/内核可以只读访问同一缓存行(MESI 缓存一致性协议中的“共享”)。 一旦内循环test 操作读取false,外循环中的test_and_set 将获得锁,并且仅在此时,需要std::memory_order_acquire。 因此,在内循环中对test 操作使用“获取”是没有意义的,并且对memory_order_acquire 需要额外CPU 指令(例如PowerPC)的较弱架构感到悲观。
    由于内部循环是关于优化的,它应该使用std::memory_order_relaxed

        void lock() {
            while (flag.test_and_set(std::memory_order_acquire)) {
                while (flag.test(std::memory_order_relaxed)); //Spin here
            }
        }
    

    为了进一步优化,可以使用独立的栅栏,以便在线程获取锁定标志后调用 CPU 栅栏指令。

    class Spinlock
    {
        std::atomic_flag flag{};
    public:
        void lock() {
            while (flag.test_and_set(std::memory_order_relaxed)) {
                while (flag.test(std::memory_order_relaxed)); //Spin here
            }
            std::atomic_thread_fence(std::memory_order_acquire);
        }
        void unlock() {
            flag.clear(std::memory_order_release);
        }
    };
    

    内部循环是一种有用的优化吗? - 也许.. 不会有什么坏处,但如果经常到达内部循环,就会出现一些争用,并且自旋锁可能不是正确的工具。

    【讨论】:

      猜你喜欢
      • 2010-12-09
      • 1970-01-01
      • 2012-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-27
      • 1970-01-01
      • 2015-05-26
      相关资源
      最近更新 更多