为什么std :: mutex在OSX上这么慢?

时间:2014-04-06 19:43:55

标签: c++ multithreading macos

我有以下基准:https://gist.github.com/leifwalsh/10010580

本质上,它会旋转k个线程,然后每个线程使用自旋锁和k执行大约1600万/ std::mutex个锁定/增量/解锁周期。在OSX上,std::mutex在争用时比自旋锁慢得多,而在Linux上它具有竞争力或者更快。

OSX:

spinlock 1:     334ms
spinlock 2:     3537ms
spinlock 3:     4815ms
spinlock 4:     5653ms
std::mutex 1:   813ms
std::mutex 2:   38464ms
std::mutex 3:   44254ms
std::mutex 4:   47418ms

Linux的:

spinlock 1:     305ms
spinlock 2:     1590ms
spinlock 3:     1820ms
spinlock 4:     2300ms
std::mutex 1:   377ms
std::mutex 2:   1124ms
std::mutex 3:   1739ms
std::mutex 4:   2668ms

处理器不同,但不是 不同(OSX是Intel(R)Core(TM)i7-2677M CPU @ 1.80GHz,Linux是Intel(R)Core(TM)i5- 2500K CPU @ 3.30GHz),这看起来像是一个库或内核问题。有人知道缓慢的根源吗?

为了澄清我的问题,我理解"有不同的互斥体实现针对不同的事情进行优化,这不是一个问题,它是预期的"。这个问题是:实现中导致这种情况的实际差异是什么?或者,如果它是硬件问题(可能缓存在macbook上慢得多),那也是可以接受的。

3 个答案:

答案 0 :(得分:12)

您只是衡量图书馆选择的公平交易吞吐量。该基准是非常人为的,并且会惩罚任何提供任何公平性的企图。

实施可以做两件事。它可以让同一个线程连续两次获取互斥锁,或者它可以更改哪个线程获取互斥锁。这个基准测试会严重影响线程的变化,因为上下文切换需要时间,因为将互斥锁和val从缓存中ping到缓存需要花费时间。

最有可能的是,这只是展示了实施必须做出的不同权衡。它大大奖励那些更愿意将互斥锁返回给最后一个持有它的线程的实现。该基准测试甚至奖励那些浪费CPU来实现的实现!它甚至奖励浪费CPU以避免上下文切换的实现,即使CPU可以执行其他有用的工作!它也不会影响核心间流量的实现,这可能会降低其他无关线程的速度。

此外,实施互斥体的人通常认为在非竞争情况下的性能比竞争情况下的性能更重要。在这些情况之间可以进行许多权衡,例如假设可能存在线程等待或特定检查是否存在。基准测试仅(或至少,几乎只)测试通常被推销的情况,以支持更常见的情况。

直言不讳地说,这是一个毫无意义的基准,无法识别问题。

具体解释几乎可以肯定,Linux实现是一个自旋锁/ futex混合,而OSX实现是常规的,等同于锁定内核对象。 Linux实现的spinlock部分倾向于允许刚刚发布互斥锁的同一个线程再次锁定它,这是你的基准测试得到很大回报。

答案 1 :(得分:3)

除了吞吐量/适应性评论之外,David Schwartz基本上是正确的。它在Linux上实际上要快得多,因为它使用的是futex&争用呼叫的开销要小得多。这意味着在无竞争的情况下,它只是进行函数调用,原子操作和回报。如果你的大部分锁是无竞争的(这通常是你在许多真实世界的程序中看到的典型行为),那么获得锁定基本上是免费的。即使在竞争情况下,它基本上是函数调用,系统调用+原子操作+向列表添加1个线程(系统调用是操作的昂贵部分)。如果在系统调用期间释放了互斥锁,则该函数立即返回,而不会排入等待列表。

在OSX上,没有futex。获取互斥锁需要始终与内核通信。而且,OSX是一种微内核混合体。这意味着要与内核通信,您需要向其发送消息。这意味着您进行数据编组,系统调用,将数据复制到单独的缓冲区。然后在某个时刻内核来了,解组数据&获得锁和&给你发回一条消息。因此,在无竞争的情况下,它的重量更重。在竞争情况下,它取决于您等待锁定的时间长度:您等待的时间越长,在整个运行时间内分摊的锁定操作就越便宜。

在OSX上,有一种更快的机制叫做调度队列,但它需要重新思考你的程序是如何工作的。除了使用无锁同步(即无竞争情况永远不会跳转到内核),它们还可以进行线程池和工作安排。此外,它们还提供异步分派,使您无需等待锁定即可安排作业。

答案 2 :(得分:2)

您需要在两个系统上使用相同的STL实现。这可能是libc ++或pthread_mutex _ *()中的问题。

其他海报在OS X上关于互斥锁的说法是完全的谎言。是的,Mach锁定集和信号量需要每次操作的系统调用。但除非您明确使用lockset或信号量Mach API,否则这些API不会在您的应用程序中使用。

OS X的libpthread使用__psynch_ * BSD系统调用,远程匹配Linux futex。在无竞争的情况下,libpthread不进行系统调用来获取互斥锁。仅使用诸如cmpxchg之类的指令。

来源:libpthread源代码和我自己的知识(我是Darling的开发人员)。