互斥锁比自旋锁更快。

这是关于自旋锁的后续文章,之前的帖子指出自旋锁有一些很糟糕的最坏情况行为,不应盲目使用自旋锁,若使用睡眠互斥锁或完全避免阻塞很麻烦的话。

Where Does The Misconception Come From?

人们认为自旋锁更快是因为最简单的互斥锁在进入和退出临界区时会进行lock/unlock系统调用,将同步工作交给内核,而系统调用较慢,若临界区长度小于两个系统调用的长度,自旋会更快。不过现代互斥锁实现在无竞争时可避免所有系统调用,只在有等待者时才调用内核,且自旋锁大小通常更小,但也有办法解决其效率问题。

Benchmark

通过代码测试不同互斥锁和自旋锁的性能,假设互斥锁更快,选择极短临界区(仅递增计数器)的工作负载,用数组和CachePadded避免虚假共享,手动设置种子的简单伪随机数生成器使基准测试更可重复。

Results

测试了std::sync::Mutexparking_lot::Mutexspin::MutexAmdSpinlock,在不同竞争程度下的结果如下:

  • 极端竞争:std::sync::Mutex平均 97ms,最小 38ms,最大 103ms;parking_lot::Mutex平均 68ms,最小 32ms,最大 72ms;spin::Mutex平均 142ms,最小 69ms,最大 217ms;AmdSpinlock平均 127ms,最小 50ms,最大 219ms。
  • 重度竞争:std::sync::Mutex平均 21ms,最小 11ms,最大 23ms;parking_lot::Mutex平均 10ms,最小 6ms,最大 11ms;spin::Mutex平均 55ms,最小 7ms,最大 161ms;AmdSpinlock平均 40ms,最小 6ms,最大 123ms。
  • 轻度竞争:std::sync::Mutex平均 13ms,最小 8ms,最大 15ms;parking_lot::Mutex平均 6ms,最小 3ms,最大 8ms;spin::Mutex平均 37ms,最小 4ms,最大 115ms;AmdSpinlock平均 39ms,最小 2ms,最大 127ms。
  • 无竞争:std::sync::Mutex平均 15ms,最小 8ms,最大 27ms;parking_lot::Mutex平均 7ms,最小 4ms,最大 9ms;spin::Mutex平均 5ms,最小 4ms,最大 8ms;AmdSpinlock平均 6ms,最小 5ms,最大 10ms。

    Analysis

  • 自旋锁在 Linux 上的方差很大。
  • 无竞争时互斥锁和自旋锁差别不大。
  • 重度竞争时互斥锁优于自旋锁,原因是自旋锁线程可能被抢占,且多个线程竞争同一临界区时,互斥锁有等待线程队列,内核会确保只有一个线程处于唤醒状态。
  • 即使重度竞争,自旋锁也可能很快。
  • 扰乱自旋锁所需的竞争量似乎很小。

    Disclaimer

    每个基准测试仅涵盖可能配置的一小部分,不能得出互斥锁总是更快的结论,例如在中断被禁用等情况下,自旋锁可能更好,且基准测试可能未测量到预期的内容。

    Reading List

    提供了一些关于互斥锁和自旋锁的相关文档和讨论链接。

阅读 84
0 条评论