key_person<em></ 阅读(31) 评论(0)


概述

本次主要是测试使用互斥锁,锁定非临界区带来的性能消耗。

在我们写代码时,有时候通过逻辑的设计,可以使代码中临界区在80%以上不会同时访问。但是从理论上来说,在极端或者概率很低的情况下它是可能成为临界区的。处于程序的稳定性考虑,同样是需要加锁的。

但是最近在看disruptor文档时,文献提到:

即使不是临界资源,只要调用了锁就会大幅度的降低性能。
而我之前在项目中的代码,总是会考虑逻辑上减少多线程去竞争同一个锁,这难道是在做无用功?

文中采用的是简单的做5亿次++操作,考虑到其是用Java实现的,因此此处采用C来实现,实践来检验一下结果


如果有资源竞争,肯定会导致性能下降。因此我们主要对比进入“假临界区"的场景。


测试代码:


点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<time.h>
  3. #include<sys/time.h>
  4. #include<unistd.h>
  5. #include<string.h>

  6. #include<pthread.h>


  7. unsigned long gtimes = 2 * 1000 * 1000 * 1000;
  8. unsigned long i;

  9. struct timeval startTime, endTime;

  10. pthread_mutex_t gmutex; //ensure not a stack varible;

  11. void start_time()
  12. {
  13.     gettimeofday(&startTime, NULL);
  14. }

  15. void end_time()
  16. {
  17.     gettimeofday(&endTime, NULL);
  18. }

  19. double spend_time()
  20. {
  21.     return 1000 * (endTime.tv_sec - startTime.tv_sec) +
  22.         (endTime.tv_usec - startTime.tv_usec) / 1000.0f;
  23. }

  24. void* test_thread(void* argv)
  25. {
  26.     i = gtimes;

  27.     start_time();
  28.     while(i--);
  29.     end_time();

  30.     printf(" a thread cost time: %.2f ms\n", spend_time());

  31.     return NULL;
  32. }

  33. void* test_lockthread(void* argv)
  34. {
  35.     i = gtimes;
  36.     pthread_mutex_init(&gmutex,NULL);

  37.     start_time();

  38.     pthread_mutex_lock(&gmutex);
  39.     while(i--);
  40.     pthread_mutex_unlock(&gmutex);

  41.     end_time();

  42.     pthread_mutex_destroy(&gmutex);
  43.     printf(" a thread with a pthread_mutex, cost time: %.2f ms\n", spend_time());

  44.     return NULL;
  45. }
  46. int main(int argc, char *argv[])
  47. {
  48.     pthread_t pid;

  49.     //pthread_create(&pid, NULL, test_thread, NULL);
  50.     pthread_create(&pid, NULL, test_lockthread, NULL);

  51.     pthread_join(pid, NULL);

  52.     test_thread(NULL);
  53.     test_lockthread(NULL);

  54.     return 0;
  55. }


测试结果:

  不加锁 加锁 效率对比 绝对值 加锁在不同线程
1 990.86 1007.29 1.66% 16.43 987.79
2 996.13 997.04 0.09% 0.91 1001.21
3 988.47 989.19 0.07% 0.72 982.72
4 993.6 992.02 -0.16% -1.58 986.94
5 984.85 984.57 -0.03% -0.28 989.66
6 991.59 986.75 -0.49% -4.84 992.94
7 986.68 986.72 0.00% 0.04 983.4
8 989.16 991.17 0.20% 2.01 987.69
9 987.22 1001.31 1.43% 14.09 985.03
10 986.27 984.09 -0.22% -2.18 987.14


从上表可以看出:
如果两个场景在不同的线程中,没有可比性:两者差值不同。
再考虑到进程调度。理论上偏差也比较大

如果是同一个线程中,除了第1和第9组数据,差距都不是很大:
最大偏差<2ms,偏差率<0.5%.
多数偏差<1ms,偏差率<0.1%.

那么另外两组误差在哪里呢?同样是时间片。Linux中时间片是10ms。
在程序中,两个函数是挨着执行的,如果第一个函数执行完成之后,在执行第二个函数的start_time后时间片到期,此时就会多消耗一个时间片。
那么我们将后一个函数减去时间片,则基本上可以在接收的范围内——实际上还会有至少两次线程切换

1 990.86 997.29 0.65% 6.43 987.79
9 987.22 991.31 0.41% 4.09 985.03


结论:

C 中的锁即使进入临界区,实际没有发生资源争用,基本上等同于进入非临界区。

但是性能消耗肯定是有的,应该是锁底层实现的首先自旋的时候会识别出来资源可用!