免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 27140 | 回复: 39

[C] 【最终版 & 总结】自实现自旋锁 与 mutex,spinlock比较(结果令人吃惊) [复制链接]

论坛徽章:
0
发表于 2011-07-29 11:39 |显示全部楼层
本帖最后由 ydfgic 于 2011-08-01 13:58 编辑

总结一下:
最后修改了一下测试代码,比较方便的设置线程数,选择测试对象,设置delay的参数。
经过测试发现我的实现测试的结果时间非常稳定,2个,10个,20个乃至40个线程的差别很小,几乎达到了与线程数无关,同样mutex也是和线程数关系不大,但是pthread_spinlock_t对线程数敏感,线程多的情况下,效率会降低很多。
同样增加加锁的粒度,对测试结果也有影响,当粒度很小的情况下,我的实现是mutex的4-5倍快,但是粒度很大的情况,比如我设置到delay的循环次数为1000时,效率是mutex的两倍多快,但是cpu更忙些。spinlock的速度就不提了,很低。
结论:
如果是比较大粒度的加锁肯定是mutex首选,虽然性能中庸,但是它因为会休眠挂起,不占用cpu,对系统影响小。
如果是比较细粒度的加锁可以用我实现的lock,对线程数量几乎无关,效率极高,可能是因为实现简单,最大效率的做到了切换cpu,保证一个线程执行,减少了多余环节。
pthread_spinlock_t的局限性太大,如果线程多的情况下,会造成性能的很大程度的损失。同时还仅限于小粒度的加锁情况。

给出测试数据:
1)20线程,0delay
我的:time ./myspinlock_O3.out 20 0 0
real    0m0.323s
user    0m0.364s
sys     0m0.276s

mutex:time ./myspinlock_O3.out 20 1 0
real    0m1.634s
user    0m1.972s
sys     0m1.264s

spinlock: time ./myspinlock_O3.out 20 2 0
real    0m6.259s
user    0m12.477s
sys     0m0.004s

2)20线程,100 delay
我的:time ./myspinlock_O3.out 20 0 100
real    0m2.965s
user    0m3.268s
sys     0m2.636s

mutex:time ./myspinlock_O3.out 20 1 100
real    0m6.493s
user    0m6.344s
sys     0m6.604s

spinlock: time ./myspinlock_O3.out 20 2 100
real    0m15.760s
user    0m31.378s
sys     0m0.004s

3)10线程,0delay
我的:time ./myspinlock_O3.out 10 0 0
real    0m0.318s
user    0m0.372s
sys     0m0.248s
mutex:time ./myspinlock_O3.out 10 1 0
real    0m1.511s
user    0m1.808s
sys     0m1.200s
spinlock:time ./myspinlock_O3.out 10 2 0
real    0m3.625s
user    0m7.224s
sys     0m0.004s

4)2线程,0delay
我的:time ./myspinlock_O3.out 2 0 0
real    0m0.323s
user    0m0.376s
sys     0m0.184s
mutex:time ./myspinlock_O3.out 2 1 0
real    0m1.453s
user    0m1.688s
sys     0m1.136s
spinlock:time ./myspinlock_O3.out 2 2 0
real    0m0.819s
user    0m1.624s
sys     0m0.004s


最终版的实现
  1. #include<stdint.h>
  2. #include<unistd.h>

  3. typedef volatile uint32_t spinlock_t;

  4. #define MY_SPINLOCK_INITIALIZER 0

  5. #define spinlock_lock(lock) do{  \
  6.     while(!__sync_bool_compare_and_swap(lock, 0, 1))    \
  7.         sched_yield();  \
  8. }while(0)

  9. #define spinlock_unlock(lock) do{    \
  10.     *lock = 0;  \
  11. }while(0)
复制代码
最终版的测试代码
  1. #include"myspinlock.h"

  2. // gcc -Wall -g -O3 -o myspinlock.out myspinlock.c -lpthread


  3. /////////////////////////                test  

  4. my_spinlock_t lock =  MY_SPINLOCK_INITIALIZER;
  5. volatile int cnt = 0;

  6. #include<pthread.h>
  7. #include<stdio.h>
  8. #include <stdlib.h>

  9. #define  TOTAL 1000000 * 20
  10. int NR;
  11. int DELAY_CNT = 100;

  12. void * fun1(void * arg)
  13. {
  14.         int i = 0, id = *(int*)arg;
  15.         printf("thread:%d\n",id);
  16.         for(; i < NR; i++)
  17.         {
  18.                 spinlock_lock(&lock);
  19.                 cnt++;
  20.                 int j = 0;
  21.                 for (; j < DELAY_CNT; j++) {
  22.                         *foo = (*foo * 33) + 17;
  23.                 }
  24.                 spinlock_unlock(&lock);
  25.         }
  26.         printf("thread:%d over, lock:%d\n",id, lock);
  27.         return 0;
  28. }

  29. pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

  30. void * fun2(void * arg)
  31. {
  32.         int i = 0, id = *(int*)arg;
  33.         printf("thread:%d\n",id);
  34.         for(; i < NR; i++)
  35.         {
  36.                 pthread_mutex_lock(&mlock);
  37.                 cnt++;
  38.                 int j = 0;
  39.                 for (; j < DELAY_CNT; j++) {
  40.                         *foo = (*foo * 33) + 17;
  41.                 }
  42.                 pthread_mutex_unlock(&mlock);
  43.         }
  44.         printf("thread:%d over, lock:%d\n",id, lock);
  45.         return 0;
  46. }

  47. pthread_spinlock_t splock;

  48. void * fun3(void * arg)
  49. {
  50.         int i = 0, id = *(int*)arg;
  51.         printf("thread:%d\n",id);
  52.         for(; i < NR; i++)
  53.         {
  54.                 pthread_spin_lock(&splock);
  55.                 cnt++;
  56.                 int j = 0;
  57.                 for (; j < DELAY_CNT; j++) {
  58.                         *foo = (*foo * 33) + 17;
  59.                 }
  60.                 pthread_spin_unlock(&splock);
  61.         }
  62.         printf("thread:%d over, lock:%d\n",id, lock);
  63.         return 0;
  64. }

  65. int N = 20;
  66. int main(int c, char * s[])
  67. {
  68.         int which = 0;
  69.         if(c > 1)
  70.         {
  71.                 //线程数
  72.                 N = atoi(s[1]);
  73.                 if(N > 20 || N <= 1) N = 10;
  74.         }
  75.         if(c > 2)
  76.         {
  77.                 //which func?
  78.                 which = atoi(s[2]);
  79.                 if(which > 2 || which < 0) which = 0;
  80.         }
  81.         if(c > 3)
  82.         {
  83.                 //delay param
  84.                 DELAY_CNT = atoi(s[3]);
  85.                 if(DELAY_CNT > 10000 || DELAY_CNT < 0) DELAY_CNT= 100;
  86.         }

  87.         pthread_t id[N];
  88.         int args[N];
  89.         int i = 0;
  90.         void * (*fun[])(void*) = { fun1,fun2,fun3};
  91.         pthread_spin_init(&splock,0);
  92.         NR = TOTAL / N;

  93.         for(;i<N;++i){
  94.                 args[i] = i;
  95.                 pthread_create(&id[i],NULL,fun[which],&args[i]);
  96.         }

  97.         for(i=0;i<N;++i){
  98.                 printf("join thread:%d\n", i);
  99.                 pthread_join(id[i],NULL);
  100.                 printf("join thread:%d done\n", i);
  101.         }

  102.         printf("cnt = %d, should be %d\n",cnt, N * NR);
  103.         return 0;
  104. }
复制代码
===============================================
先前的更新仅仅做为参考

更新
重新修改了我的实现,加入了放弃时间片的情况,测试结果,几乎是mutex的2-3倍效率
real    0m0.431s
user    0m0.604s
sys     0m0.240s

我想这个应该就会是我理想中的最终版本了,起码可以抛弃 pthread 库的mutex实现一些简单的加锁的功能。

代码:
  1. #ifndef MY_SPINLOCK_H
  2. #define MY_SPINLOCK_H

  3. #include<stdint.h>
  4. #include<unistd.h>

  5. typedef volatile uint32_t my_spinlock_t;

  6. #define MY_SPINLOCK_INITIALIZER 0
  7. #define DELAY_NR 10000

  8. static uint32_t bar = 13;
  9. static uint32_t *foo = &bar;

  10. #define do_hash(a) do{  \
  11. (a) = ((a)+0x7ed55d16) + ((a)<<12); \
  12. (a) = ((a)^0xc761c23c) ^ ((a)>>19); \
  13. (a) = ((a)+0x165667b1) + ((a)<<5);  \
  14. (a) = ((a)+0xd3a2646c) ^ ((a)<<9);  \
  15. (a) = ((a)+0xfd7046c5) + ((a)<<3);  \
  16. (a) = ((a)^0xb55a4f09) ^ ((a)>>16); \
  17. }while(0)

  18. #define my_spinlock_lock(lock) do{  \
  19.     while(!__sync_bool_compare_and_swap(lock, 0, 1))    \
  20.     {   \
  21.         while(*lock)    \
  22.         {   \
  23.             do_hash(*foo);  \
  24.             if((*foo % 11) == 1)    \
  25.                 sched_yield();  \
  26.         }   \
  27.     }   \
  28. }while(0)

  29. #define my_spinlock_unlock(lock) do{    \
  30.     *lock = 0;  \
  31. }while(0)

  32. #endif
复制代码
=======================================

最近在研究原子操作,按网上一些资料实现了个自旋锁
拿来和 posix 的mutex,spinlock 一起测,结果出乎我意料。
mutex的成绩非常好,我自己实现的稍微差点,posix 的pthread_spinlock_t的结果比较差。
这个真没想到,mutex的效率这么高,看到这个结果我都觉得不相信自己的眼睛了
还是印证了,不要靠自己感觉,实际数据才是最真实的。

谁能解释一下,谢谢~

环境:
uname -a
Linux bsd02 2.6.35.9 #1 SMP Tue Jan 11 02:09:50 EST 2011 x86_64 GNU/Linux
双核 Pentium(R) Dual-Core  CPU      E5400  @ 2.70GHz

并发20个线程测试,结果:
我的实现:
real    0m1.659s
user    0m3.276s
sys     0m0.000s

mutex:
real    0m1.481s
user    0m1.164s
sys     0m1.764s

pthread spinlock:
real    0m6.171s
user    0m12.301s
sys     0m0.004s

论坛徽章:
0
发表于 2011-07-29 11:43 |显示全部楼层
spinlock有这么差吗?是不是没用对

论坛徽章:
0
发表于 2011-07-29 11:45 |显示全部楼层
本帖最后由 ydfgic 于 2011-08-01 10:45 编辑

这是最原始版本的代码,最终代码1楼以更新
======
附上代码:
  1. #ifndef MY_SPINLOCK_H
  2. #define MY_SPINLOCK_H

  3. #include<stdint.h>

  4. typedef volatile uint32_t my_spinlock_t;

  5. #define MY_SPINLOCK_INITIALIZER 0
  6. #define DELAY_NR 10000

  7. static uint32_t bar = 13;
  8. static uint32_t *foo = &bar;

  9. #define my_spinlock_lock(lock) do{  \
  10.     int i;  \
  11.     while(!__sync_bool_compare_and_swap(lock, 0, 1))    \
  12.     {   \
  13.         i = 0;  \
  14.         while(i++ < DELAY_NR)   \
  15.             *foo = (*foo * 33) + 17;    \
  16.     }   \
  17. }while(0)

  18. #define my_spinlock_unlock(lock) do{    \
  19.     *lock = 0;  \
  20. }while(0)


  21. #endif
复制代码
  1. #include"myspinlock.h"
  2. my_spinlock_t lock =  MY_SPINLOCK_INITIALIZER;
  3. volatile int cnt = 0;

  4. #include<pthread.h>
  5. #include<stdio.h>
  6. // gcc -Wall -g -o myspinlock.out myspinlock.c -lpthread

  7. #define  NR 1000000

  8. void * fun1(void * arg)
  9. {
  10.     int i = 0, id = *(int*)arg;
  11.     printf("thread:%d\n",id);
  12.     for(; i < NR; i++)
  13.     {
  14.         my_spinlock_lock(&lock);
  15.         cnt++;
  16.         my_spinlock_unlock(&lock);
  17.     }
  18.     printf("thread:%d over, lock:%d\n",id, lock);
  19.     return 0;
  20. }

  21. pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER;

  22. void * fun2(void * arg)
  23. {
  24.     int i = 0, id = *(int*)arg;
  25.     printf("thread:%d\n",id);
  26.     for(; i < NR; i++)
  27.     {
  28.         pthread_mutex_lock(&mlock);
  29.         cnt++;
  30.         pthread_mutex_unlock(&mlock);
  31.     }
  32.     printf("thread:%d over, lock:%d\n",id, lock);
  33.     return 0;
  34. }
  35. pthread_spinlock_t splock;

  36. void * fun3(void * arg)
  37. {
  38.     int i = 0, id = *(int*)arg;
  39.     printf("thread:%d\n",id);
  40.     for(; i < NR; i++)
  41.     {
  42.         pthread_spin_lock(&splock);
  43.         cnt++;
  44.         pthread_spin_unlock(&splock);
  45.     }
  46.     printf("thread:%d over, lock:%d\n",id, lock);
  47.     return 0;
  48. }

  49. int N = 20;
  50. int main(int c, char * s[])
  51. {
  52.     pthread_t id[N];
  53.     int args[N];
  54.     int i = 0;
  55.     void * (*fun[])(void*) = { fun1,fun2,fun3};
  56.     if(--c > 2) c = 0;
  57.     printf("c:%d\n",c);
  58.     pthread_spin_init(&splock,0);

  59.     for(;i<N;++i){
  60.         args[i] = i;
  61.         pthread_create(&id[i],NULL,fun[c],&args[i]);
  62.     }

  63.     for(i=0;i<N;++i){
  64.         printf("join thread:%d\n", i);
  65.         pthread_join(id[i],NULL);
  66.         printf("join thread:%d done\n", i);
  67.     }

  68.     printf("%d\n",cnt);
  69.     return 0;
  70. }
复制代码

论坛徽章:
0
发表于 2011-07-29 11:47 |显示全部楼层
回复 2# int-main
我贴代码了。
spinlock真的很差,这是个奇迹,它就是发生了。

论坛徽章:
0
发表于 2011-07-29 11:58 |显示全部楼层
最关键的__sync_bool_compare_and_swap是什么

你这是每次都lock成功的例子,测试结果没代表性

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2011-07-29 13:05 |显示全部楼层
spin lock, 翻译自选锁不好,要翻译成 “忙等锁”,
忙等,必然用在非常变态的场合。

论坛徽章:
0
发表于 2011-07-29 13:12 |显示全部楼层
最关键的__sync_bool_compare_and_swap是什么

你这是每次都lock成功的例子,测试结果没代表性
deadlylight 发表于 2011-07-29 11:58



    __sync_bool_compare_and_swap  是 GCC 内建的原子操作函数, 执行CAS 操作,也就是 比较如果相等就swap,并且返回true,否则返回false。所以失败的线程都会进入while循环里去,忙等。

还有个你注意到没,posix 的spinlock效率还低下些。

论坛徽章:
0
发表于 2011-07-29 13:17 |显示全部楼层
spin lock, 翻译自选锁不好,要翻译成 “忙等锁”,
忙等,必然用在非常变态的场合。
群雄逐鹿中原 发表于 2011-07-29 13:05



    我觉得我在这里测试的情况也属于变态,呵呵,就是一个 计数累加 就放锁了,按常规逻辑,这就是用spinlock的场景,防止线程陷落到内核态,应该效率比mutex高很多。但是事实却相反...所以很不解啊

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2011-07-29 13:22 |显示全部楼层
回复 8# ydfgic


    你的线程,在 while(i++ < DELAY_NR)  时,
被切换掉,然后另一个线程开始自旋,不是要等出人命?

论坛徽章:
0
发表于 2011-07-29 13:52 |显示全部楼层
回复 9# 群雄逐鹿中原


    好像自旋锁是这个概念吧,不放cpu在那里忙等,我已经调整了DELAY_NR的值了
网上还有个变态的版本就是 while(*lock);  我试过了效率要低些。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

SACC2019中国系统架构师大会

【数字转型 架构演进】SACC2019中国系统架构师大会,7折限时优惠重磅来袭!
2019年10月31日~11月2日第11届中国系统架构师大会(SACC2019)将在北京隆重召开。四大主线并行的演讲模式,1个主会场、20个技术专场、超千人参与的会议规模,100+来自互联网、金融、制造业、电商等领域的嘉宾阵容,将为广大参会者提供一场最具价值的技术交流盛会。

限时七折期:2019年8月31日前


----------------------------------------

大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:help@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP