简介
测试Win32下Mutex以及CriticalSection的效率
方法
创建两个线程,同时在for循环里对一个全局变量Var进行修改,并测量耗时。
代码
#pragma once
#include<Windows.h>
#include<cstdio>
#include<ctime>
#include<mutex>
namespace Test {
static int Var = 0;
static int LoopCnt = 10000;
using ThreadFunc = DWORD(*)(LPVOID);
//static auto milliseconds_now = clock;
long long milliseconds_now() {
static LARGE_INTEGER s_frequency;
static BOOL s_use_qpc = QueryPerformanceFrequency(&s_frequency);
if (s_use_qpc) {
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
return (1000LL * now.QuadPart) / s_frequency.QuadPart;
}
else {
return GetTickCount();
}
}
static HANDLE GenThread(ThreadFunc Func,
LPDWORD ThreadID, int* Inc) {
return CreateThread(
NULL, // default security attributes
0, // use default stack size
Func, // thread function name
Inc, // argument to thread function
0, // use default creation flags
ThreadID); // returns the thread identifier
}
static DWORD AddFunc_NoSync(LPVOID Param) {
int Inc = *(int*)Param;
//int Inc = 1;
for (int i = 0; i < LoopCnt; i++) {
Var += Inc;
}
return 0;
}
static HANDLE ghMutex;
static DWORD AddFunc_WinMutex(LPVOID Param) {
int Inc = *(int*)Param;
for (int i = 0; i < LoopCnt; i++) {
auto dwWaitResult = WaitForSingleObject(
ghMutex, // handle to mutex
INFINITE); // no time-out interval
Var += Inc;
ReleaseMutex(ghMutex);
}
return 0;
}
static CRITICAL_SECTION CriticalSection;
static DWORD AddFunc_WinCritical(LPVOID Param) {
int Inc = *(int*)Param;
for (int i = 0; i < LoopCnt; i++) {
EnterCriticalSection(&CriticalSection);
Var += Inc;
LeaveCriticalSection(&CriticalSection);
}
return 0;
}
static std::mutex StdMutex;
static DWORD AddFunc_StdMutex(LPVOID Param) {
int Inc = *(int*)Param;
for (int i = 0; i < LoopCnt; i++) {
StdMutex.lock();
Var += Inc;
StdMutex.unlock();
}
return 0;
}
static void StartCnt(ThreadFunc Func) {
DWORD ThreadID;
int X = 1;
auto ThreadHandle = GenThread(Func, &ThreadID, &X);
auto ThreadHandle2 = GenThread(Func, &ThreadID, &X);
WaitForSingleObject(ThreadHandle, INFINITE);
WaitForSingleObject(ThreadHandle2, INFINITE);
}
static long long NoSyncCnt() {
auto Beg = milliseconds_now();
StartCnt(AddFunc_NoSync);
auto End = milliseconds_now();
return (End - Beg);
}
static long long WinMutexCnt() {
ghMutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
auto Beg = milliseconds_now();
StartCnt(AddFunc_WinMutex);
auto End = milliseconds_now();
CloseHandle(ghMutex);
return (End - Beg);
}
static long long WinCriticalCnt() {
InitializeCriticalSection(&CriticalSection);
auto Beg = milliseconds_now();
StartCnt(AddFunc_WinCritical);
auto End = milliseconds_now();
DeleteCriticalSection(&CriticalSection);
return (End - Beg);
}
static long long StdMutexCnt() {
auto Beg = milliseconds_now();
StartCnt(AddFunc_StdMutex);
auto End = milliseconds_now();
return (End - Beg);
}
void TestNoSync() {
Var = 0;
auto Elapse = NoSyncCnt();
printf("No Sync\t\t: Var = %9d, Elapse = %4lld ms\n", Var, Elapse);
}
void TestWinMutex() {
Var = 0;
auto Elapse = WinMutexCnt();
printf("Win Mutex\t: Var = %9d, Elapse = %4lld ms\n", Var, Elapse);
}
void TestWinCritical() {
Var = 0;
auto Elapse = WinCriticalCnt();
printf("Win Ctitical\t: Var = %9d, Elapse = %4lld ms\n", Var, Elapse);
}
void TestStdMutex() {
Var = 0;
auto Elapse = StdMutexCnt();
printf("Std Mutex\t: Var = %9d, Elapse = %4lld ms\n", Var, Elapse);
}
double AvgElapse(long long(*CntFunc)()) {
int Times = 100;
long long Elapse = 0;
for(int i = 0; i < Times; i++){
Elapse += CntFunc();
}
return ((double)Elapse) / Times;
}
static void LockBenchmark(){
//{
// printf("### No Sync ###\n");
// auto Elapse = AvgElapse(NoSyncCnt);
// printf("No Sync\t\t: Var = %9d, Elapse = %f ms\n", Var, Elapse);
//}
//{
// printf("### Win Mutex ###\n");
// auto Elapse = AvgElapse(WinMutexCnt);
// printf("Win Mutex\t: Var = %9d, Elapse = %f ms\n", Var, Elapse);
//}
//{
// printf("### Std Mutex ###\n");
// auto Elapse = AvgElapse(StdMutexCnt);
// printf("Std Mutex\t: Var = %9d, Elapse = %f ms\n", Var, Elapse);
//}
{
printf("### Win Critical ###\n");
auto Elapse = AvgElapse(WinCriticalCnt);
printf("Win Critical\t: Var = %9d, Elapse = %f ms\n", Var, Elapse);
}
}
}
// LoopCnt = 10000000, Times = 100, 2 thread, Lock Outer
// No Sync : 44.37 ms
// Win Mutex : 27.30 ms
// Std Mutex : 27.31 ms
// Win Critical : 26.80 ms
// LoopCnt = 10000, Times = 100, 2 thread, Lock Inner
// No Sync : 0.12 ms
// Win Mutex : 45.83 ms
// Std Mutex : 2.53 ms
// Win Critical : 0.68 ms
结果
测试一:在for循环之外加锁,即
static DWORD AddFunc_WinCritical(LPVOID Param) {
int Inc = *(int*)Param;
EnterCriticalSection(&CriticalSection);
for (int i = 0; i < LoopCnt; i++) {
Var += Inc;
}
LeaveCriticalSection(&CriticalSection);
return 0;
}
条件:LoopCnt = 10000000, Times = 100, 2 thread, Lock Outer
| Avg Elapse/ms | |
|---|---|
| 不加锁 | 44.37 |
| Win32 Mutex | 27.30 |
| Std Mutex | 27.31 |
| Win32 CriticalSection | 26.80 |
测试二:在for循环之内加锁,即:
static DWORD AddFunc_WinCritical(LPVOID Param) {
int Inc = *(int*)Param;
for (int i = 0; i < LoopCnt; i++) {
EnterCriticalSection(&CriticalSection);
Var += Inc;
LeaveCriticalSection(&CriticalSection);
}
return 0;
}
条件:LoopCnt = 10000, Times = 100, 2 thread, Lock Inner
| Avg Elapse/ms | |
|---|---|
| 不加锁 | 0.12 |
| Win32 Mutex | 45.83 |
| Std Mutex | 2.53 |
| Win32 CriticalSection | 0.68 |
Note: 不加锁的情况下Var的值是没有同步的,也就是不一定正确的。
结论
测试一
T ( W i n 32 C r i t i c a l S e c t i o n ) < T ( W i n 32 M u t e x ) < T ( S t d M u t e x ) < T ( N o L o c k ) T(Win32 CriticalSection)<T(Win32 Mutex)<T(StdMutex)<T(NoLock) T(Win32CriticalSection)<T(Win32Mutex)<T(StdMutex)<T(NoLock)
之前看到一篇文章说CriticalSection不用切换到内核态,而Mutex需要,因此CriticalSection会比较快,在这里被验证了。
不加锁是最慢的,这很奇怪,有篇文章有个解释,虽然是在linux上的,但在windows上应该有这个可能性。看完文章,我的理解是操作系统有用户态和内核态之间的切换和进程上下文切换这两种切换,加了锁之后进程就不会被切换(优先级高?),这带来的速度增益超过了一次用户态和内核态之间的切换的开销,所以速度反而变快了。这是个需要验证的观点。
测试二
T ( N o L o c k ) < T ( W i n 32 C r i t i c a l S e c t i o n ) < T ( S t d M u t e x ) < < T ( W i n 32 M u t e x ) T(NoLock)<T(Win32 CriticalSection)<T(StdMutex)<<T(Win32 Mutex) T(NoLock)<T(Win32CriticalSection)<T(StdMutex)<<T(Win32Mutex)
测试二与测试一的结果在大小关系方面区别较大。首先测试二的LoopCnt是10000,远小于测试一,这是因为在循环内加锁对于Win32Mutex来说非常慢,因此减小次数。两次测试的数据不能直接比较,要比较的是大小关系。
在测试一中,CriticalSection和Mutex差异不大,但在测试二里明显有了差异,这是因为我们的锁是加在循环里面(导致使用Mutex时频繁地切换内核态和用户态?),而CriticalSection则不用切换。
在测试一中,Win32Mutex和StdMutex相差不大,但在测试二里差异巨大,猜测标准库的Mutex做了优化?或者Win32下的Mutex有更高效的用法?
在测试二中,Win32Mutex远远慢于不加锁的情况,说明如果频繁切换内核-用户态对性能的影响很大。
总结
不考虑跨平台的话,在windows下,需要加锁时,CriticalSection优于Mutex。
TODO
这里线程数量只有2个,比较简单,也许可以增加到几十个来看看结果是否一样。
本文通过对比测试Win32环境下的Mutex和CriticalSection,发现CriticalSection在大多数情况下速度更快,尤其是在循环内加锁的情况下。测试结果显示,不加锁的性能优于Win32 Mutex,而Win32CriticalSection在某些场景下接近不加锁的性能。结论指出,在Windows下,CriticalSection通常是更好的选择。

2141

被折叠的 条评论
为什么被折叠?



