std::mutex 和 QMutex 在 MinGW 64 中的性能(posix 线程版本)
Performance of std::mutex and QMutex in MinGW 64 (posix thread version)
我尝试用std::mutex
替换我的应用程序(monte carlo模拟)中的QMutex
,令人惊讶的是,计算速度除以3。互斥量locking/unlocking 性能成本从可忽略不计上升到大约 66% 的线程时间。
我深入研究了实现源。我最初认为两者都是 Win32 线程的包装器(std::thread
有一个额外的 pthread 层),但实际上 Qt 没有为互斥量使用任何内核函数,并且有它自己的基于原子变量的内部实现。这个好像快多了。
- 是否真的可以只用原子变量来完全实现互斥?它有限制吗?
- 为什么这个在STL中没有使用?
- 在我的案例中,完全避免互斥锁的准则是什么?我在浮点缓冲区上累积(加法运算)值,在多个线程之间共享
谢谢
听起来 QMutex 是用自旋锁实现的。回答您的问题:
- 是的,这是可能的。它有局限性,例如参见 [=10=]。
- 只用原子就可以写一个自旋锁;您不能从标准库中的任何其他内容编写真正的互斥量。
- 不知道你的算法是做什么的,这很难说。但看起来您可以从阅读本书中获益:Is Parallel Programming Hard, And, If So, What Can You Do About It?。特别是 Counting 章节似乎与您所描述的大致对应。 TL;DR 您将不得不尝试该章中的不同算法,以了解哪种算法更适合您的特定情况。
Qt is not using any kernel functions for the mutexes, and has its own internal implementation based on atomic variables
如果互斥量已经被锁定,Qt 当然会调用 OS 让线程等待 .
这里的想法是,在良好的多线程代码中,等待已锁定的互斥锁的可能性极低; 要优化的常见情况是无竞争的互斥锁,必须尽可能快地保持它。
因此,QMutex 是围绕 Linux futex
系统调用设计的,并使用原子和无锁编程。在无竞争的情况下,不需要系统调用,只需要一些巧妙的原子编程;在有争议的情况下,系统调用 是 必需的(对 wait/wake 线程),而这正是 Qt 使用的:
- pthread_mutex_* / pthread_cond_* 在通用 Unix 上
- futex 在 Linux
- WaitForSingleObjectEx 在 Windows
- semaphore_* 在 Mac
有关 QMutex 设计背后的更多信息,请参阅 here。
为什么STL不使用类似的方法?我不知道。可能是因为有 native_handle
函数在所有情况下都应该 return "something" ,即使当互斥体被解锁或锁定但没有竞争时,所以它 总是 使用系统调用。
(无论如何,我对 Qt 优于 std::mutex
并不感到惊讶。如果不是,QMutex 将成为 std::mutex
的包装器。)
有趣的是我来这里是为了弄清楚为什么 QMutex 这么慢。至少对于高度满足的情况,与 enterCriticalSection 相比,等待单个对象显得相当慢。 TBB 包装它并且似乎比 windows.
上的 QMutex 快得多
我尝试用std::mutex
替换我的应用程序(monte carlo模拟)中的QMutex
,令人惊讶的是,计算速度除以3。互斥量locking/unlocking 性能成本从可忽略不计上升到大约 66% 的线程时间。
我深入研究了实现源。我最初认为两者都是 Win32 线程的包装器(std::thread
有一个额外的 pthread 层),但实际上 Qt 没有为互斥量使用任何内核函数,并且有它自己的基于原子变量的内部实现。这个好像快多了。
- 是否真的可以只用原子变量来完全实现互斥?它有限制吗?
- 为什么这个在STL中没有使用?
- 在我的案例中,完全避免互斥锁的准则是什么?我在浮点缓冲区上累积(加法运算)值,在多个线程之间共享
谢谢
听起来 QMutex 是用自旋锁实现的。回答您的问题:
- 是的,这是可能的。它有局限性,例如参见 [=10=]。
- 只用原子就可以写一个自旋锁;您不能从标准库中的任何其他内容编写真正的互斥量。
- 不知道你的算法是做什么的,这很难说。但看起来您可以从阅读本书中获益:Is Parallel Programming Hard, And, If So, What Can You Do About It?。特别是 Counting 章节似乎与您所描述的大致对应。 TL;DR 您将不得不尝试该章中的不同算法,以了解哪种算法更适合您的特定情况。
Qt is not using any kernel functions for the mutexes, and has its own internal implementation based on atomic variables
如果互斥量已经被锁定,Qt 当然会调用 OS 让线程等待 .
这里的想法是,在良好的多线程代码中,等待已锁定的互斥锁的可能性极低; 要优化的常见情况是无竞争的互斥锁,必须尽可能快地保持它。
因此,QMutex 是围绕 Linux futex
系统调用设计的,并使用原子和无锁编程。在无竞争的情况下,不需要系统调用,只需要一些巧妙的原子编程;在有争议的情况下,系统调用 是 必需的(对 wait/wake 线程),而这正是 Qt 使用的:
- pthread_mutex_* / pthread_cond_* 在通用 Unix 上
- futex 在 Linux
- WaitForSingleObjectEx 在 Windows
- semaphore_* 在 Mac
有关 QMutex 设计背后的更多信息,请参阅 here。
为什么STL不使用类似的方法?我不知道。可能是因为有 native_handle
函数在所有情况下都应该 return "something" ,即使当互斥体被解锁或锁定但没有竞争时,所以它 总是 使用系统调用。
(无论如何,我对 Qt 优于 std::mutex
并不感到惊讶。如果不是,QMutex 将成为 std::mutex
的包装器。)
有趣的是我来这里是为了弄清楚为什么 QMutex 这么慢。至少对于高度满足的情况,与 enterCriticalSection 相比,等待单个对象显得相当慢。 TBB 包装它并且似乎比 windows.
上的 QMutex 快得多