MinGW 64 中 std::mutex 和 QMutex 的性能(posix 线程版本)

Posted

技术标签:

【中文标题】MinGW 64 中 std::mutex 和 QMutex 的性能(posix 线程版本)【英文标题】:Performance of std::mutex and QMutex in MinGW 64 (posix thread version) 【发布时间】:2015-03-20 15:44:02 【问题描述】:

我尝试将我的应用程序(蒙特卡罗模拟)中的QMutex 替换为std::mutex,令人惊讶的是,计算速度被除以3。互斥锁/解锁性能成本从可以忽略不计上升到大约66%线程时间。

我深入研究了实现源。我最初认为两者都是 Win32 线程的包装器(为std::thread 增加了一层 pthread),但实际上 Qt 没有为互斥锁使用任何内核函数,并且有自己的基于原子变量的内部实现。这个似乎要快得多。

真的有可能完全实现只使用原子变量的互斥锁吗?有限制吗? 为什么在 STL 中不使用它? 在我的情况下,完全避免互斥锁的指导方针是什么?我在浮点缓冲区上累积(加法运算)值,在多个线程之间共享

谢谢

【问题讨论】:

【参考方案1】:

Qt 没有为互斥锁使用任何内核函数,并且有自己的基于原子变量的内部实现

当然,如果互斥锁已被锁定,Qt 会调用操作系统让线程等待

这里的想法是,在好的多线程代码中,等待已经锁定的互斥锁的机会非常低; 要优化的常见情况是非竞争互斥体,它必须尽可能快。

因此,QMutex 是围绕 Linux futex 系统调用设计的,并使用原子和无锁编程。在无竞争的情况下,不需要系统调用,只需要一些巧妙的原子编程;在有争议的情况下,系统调用必要的(等待/唤醒线程),这确实是 Qt 使用的:

pthread_mutex_* / pthread_cond_* 在通用 Unix 上 futex 在 Linux 上 WaitForSingleObjectEx 在 Windows 上 semaphore_* 在 Mac 上

有关 QMutex 设计背后的更多信息,请参阅here。

为什么 STL 不使用类似的方法?我不知道。可能是因为 native_handle 函数在所有情况下都应该返回“某物”,即使互斥锁已解锁或锁定但没有争用,所以它总是使用系统调用。

(无论如何,Qt 的性能优于 std::mutex,我并不感到惊讶。如果没有,QMutex 将成为 std::mutex 的包装器。)

【讨论】:

【参考方案2】:

听起来 QMutex 是用自旋锁实现的。回答您的问题:

是的,这是可能的。它有局限性,例如见this answer。 只需使用原子,您就可以编写自旋锁;您无法从标准库中的任何其他内容编写真正的互斥锁。 不知道你的算法是做什么的,很难说。但看起来你可以从阅读这本书中受益:Is Parallel Programming Hard, And, If So, What Can You Do About It?。 Counting 一章似乎与您所描述的大致对应。 TL;DR您必须尝试该章中的不同算法,看看哪种算法更适合您的特定情况。

【讨论】:

其实peppe是对的,Qt只是在没有并发锁的情况下使用一个原子标志来加速互斥锁。否则它使用系统互斥锁 我不能使用无锁计数方法,因为这是一个浮点加法。不幸的是,没有原子浮点数。【参考方案3】:

有趣的是,我来这里是为了弄清楚为什么 QMutex 这么慢。至少对于非常满足的情况,等待单个对象与 enterCriticalSection 相比显得相当慢。 TBB 包装了它,并且似乎比 Windows 上的 QMutex 快得多。

【讨论】:

以上是关于MinGW 64 中 std::mutex 和 QMutex 的性能(posix 线程版本)的主要内容,如果未能解决你的问题,请参考以下文章

std::mutex 和 std::shared_mutex 之间的区别

std::mutex 锁定函数和 std::lock_guard<std::mutex> 的区别?

C++11 并发指南三(std::mutex 详解)

c++ 如何将 std::mutex 和 std::lock_guard 与仿函数一起使用?

C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock

C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock