Windows平台几种同步机制的性能实证

因为最近研究单位项目的代码,看到里面用到了一种自己实现的读写锁,我对那把锁的性能没有什么概念,打算研究一下。所以有了一个念头,先把几种常用的同步机制实证一下性能,做一下技术准备。

测试思路

启动一组线程,在线程内做一个循环,每次循环都对一组全局 counter 累加。最后等所有的线程运行结束后,检查counter的正确性,并统计执行的时间。

测试环境

AMD 羿龙II 955x4,8G 内存,Win 7 64位,VS2010,编译平台x64. 

 

首先定义一组全局 counter,三个counter相互独立。线程要分别对这三个累加,全局变量,初始值都是0,期待的正确结果是这三个 counter 累加后的数值依然保持一致:

1 volatile long g_counter_1;
2 volatile long g_counter_2;
3 volatile long g_counter_3;

 

一共会启动5个线程,每个线程的循环次数是 1000000,这样上面的三个counter加完后应该都等于 5000000

#define WORK_STEPS      1000000

#define WORKER_COUNT    5

 

首先看一个没有任何锁保护的线程:

 1 DWORD WINAPI raw_worker_thread(void*)
 2 {
 3     for (int i = 0; i < WORK_STEPS; i++)
 4     {
 5         g_counter_1++;
 6         g_counter_2++;
 7         g_counter_3++;
 8     }
 9     return 0;
10 }

非优化编译下,生成的代码还是带循环的,加了优化会把循环约去,直接变成一具赋值语句。所以我们测试的时候都要关闭优化。

运行的结果:

counters: 1763635, 2277816, 1453466

Time cost 63 ticks.

 

 

 

可以看到,运行时间很快,但是结果谬以千里。

 

再看一个 InterlockedExchange 版本的,这是CPU提供的一种机制,指令有 lock前缀,在更新内存的时候锁住内存总线,防止其他CPU同时也修改同一块内存。

 1 DWORD WINAPI exchg_worker_thread(void*)
 2 {
 3     for (int i = 0; i < WORK_STEPS; i++)
 4     {
 5         InterlockedExchangeAdd(&g_counter_1, 1);
 6         InterlockedExchangeAdd(&g_counter_2, 1);
 7         InterlockedExchangeAdd(&g_counter_3, 1);
 8     }
 9     return 0;
10 }

这样就可以保证多线程累加得到正确的结果:

counters: 5000000, 5000000, 5000000
Time cost 328 ticks.

 

 

 

耗时略有增加。

 

接下来是CriticalSection版本的:

 1 CRITICAL_SECTION  g_critical_section;
 2 
 3 #define init_critical_section()     InitializeCriticalSection(&g_critical_section)
 4 #define delete_critical_section()   DeleteCriticalSection(&g_critical_section)
 5 
 6 #define enter_critical_section()    EnterCriticalSection(&g_critical_section)
 7 #define leave_critical_section()    LeaveCriticalSection(&g_critical_section)
 8 
 9 DWORD WINAPI critical_section_worker_thread(void*)
10 {
11     for (int i = 0; i < WORK_STEPS; i++)
12     {
13         enter_critical_section();
14 
15         g_counter_1++;
16         g_counter_2++;
17         g_counter_3++;
18 
19         leave_critical_section();
20     }
21     return 0;
22 }

测试程序需要在线程外调用init_critical_section() 和 delete_critical_section()

测试结果如下:

counters: 5000000, 5000000, 5000000
Time cost 374 ticks.

 

 

 

可以看到,critical_section 比 InterlockedExchange 只慢了一点点。Critical Section 是一种用户态的同步机制,不用创建内核对象,所以性能会比较好。因为这里更新的是三组变量,interlocked指令要执行3次,这种指令会锁住内存总线,还会设置 memory barrier,导致CPU同步缓存,造成性能大幅下降,因此这个操作不易频繁使用。只用了3个,就几乎抵消了 interlocked 指令相对 critical section 的性能优势。

 

CriticalSection 还提供了自旋锁的功能,我也实现了一版带自旋的CriticalSection:

 1 // use spin critical section, the spin count is adjustable.
 2 CRITICAL_SECTION  g_spin_critical_section;
 3 
 4 #define SPIN_COUNT 10
 5 
 6 #define init_spin_critical_section()    InitializeCriticalSectionAndSpinCount(&g_spin_critical_section, SPIN_COUNT)
 7 #define delete_spin_critical_section()  DeleteCriticalSection(&g_spin_critical_section)
 8 
 9 #define enter_spin_critical_section()    EnterCriticalSection(&g_spin_critical_section)
10 #define leave_spin_critical_section()    LeaveCriticalSection(&g_spin_critical_section)
11 
12 DWORD WINAPI spin_critical_section_worker_thread(void*)
13 {
14     for (int i = 0; i < WORK_STEPS; i++)
15     {
16         enter_spin_critical_section();
17 
18         g_counter_1++;
19         g_counter_2++;
20         g_counter_3++;
21 
22         leave_spin_critical_section();
23     }
24     return 0;
25 }

这里的自旋数设为10,因为我发现这个数字设的越大性能越差,无论如何设置性能都不会超越不带自旋的 CriticalSection。所以我很怀疑这个自旋的实现......

counters: 5000000, 5000000, 5000000
Time cost 421 ticks.

 

 

 

再看 Mutex 版的

 1 // use mutex, slowest way.
 2 HANDLE    g_mutex;
 3 
 4 #define create_mutex()        g_mutex = CreateMutex(NULL, FALSE, NULL);
 5 #define delete_mutex()        CloseHandle(g_mutex);
 6 
 7 #define wait_mutex()          WaitForSingleObject(g_mutex, INFINITE);
 8 #define release_mutex()       ReleaseMutex(g_mutex);
 9 
10 DWORD WINAPI mutex_worker_thread(void*)
11 {
12     for (int i = 0; i < WORK_STEPS; i++)
13     {
14         wait_mutex();
15 
16         g_counter_1++;
17         g_counter_2++;
18         g_counter_3++;
19 
20         release_mutex();
21     }
22     return 0;
23 }

这是性能最差的一版。因为Mutex 是操作系统的内核对象,因此成本会大增。<简直弱爆了 :-) >

counters: 5000000, 5000000, 5000000
Time cost 13572 ticks.

 

 

 

最后是我自己照着 linux 实现的一个自旋锁:

 1 // a spin lock
 2 volatile long g_spin_lock = 0;
 3 
 4 #define enter_spin_lock()\
 5     _GET_LOCK:\
 6     if (1 == InterlockedCompareExchange(&g_spin_lock, 1, 0))\
 7     {\
 8         while (g_spin_lock == 1);\
 9         goto _GET_LOCK;\
10     }
11 
12 #define leave_spin_lock()\
13     g_spin_lock = 0;
14 
15 DWORD WINAPI spin_lock_worker_thread(void*)
16 {
17     for (int i = 0; i < WORK_STEPS; i++)
18     {
19         enter_spin_lock();
20 
21         g_counter_1++;
22         g_counter_2++;
23         g_counter_3++;
24 
25         leave_spin_lock();
26     }
27     return 0;
28 }

本以为性能会超过 CriticalSection,结果却出乎意料:

counters: 5000000, 5000000, 5000000
Time cost 936 ticks.

 

 

 

性能真的很一般,是我哪儿没实现对吗?

 

总结

本文在Win7 64位+AMD CPU上实证了几种同步机制,依照性能从好到差排序,依次是:

    InterlockedExchange, CriticalSection, SpinCriticalSection, SpinLock, Mutex

平时使用时,尽量使用InterlockedExchange,但也不要低估 CriticalSection 的性能。也不要迷信Spinlock。

如果不是进程间的同步,不要用Mutex,太慢了。

 

 

转载于:https://www.cnblogs.com/yangchaobj/archive/2013/02/08/2909459.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值