posts - 200, comments - 8, trackbacks - 0, articles - 0

实验环境:

CPU: 双核 Intel(R) Xeon(R) CPU 5130  @ 2.00GHz,内存:1G
系统:Red Hat Enterprise Linux ES release 4 (Nahant Update 4)
内核:2.6.9-42.ELsmp

实验程序:

singlethread.c

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

  6. #define ORANGE_MAX_VALUE 1000000
  7. #define APPLE_MAX_VALUE 100000000
  8. #define MSECOND 1000000

  9. struct apple
  10. {
  11.     unsigned long long a;
  12.     unsigned long long b;
  13. };

  14. struct orange
  15. {
  16.     int a[ORANGE_MAX_VALUE];
  17.     int b[ORANGE_MAX_VALUE];
  18. };

  19. int main()
  20. {
  21.     struct apple test;
  22.     struct orange test1={{0},{0}};
  23.     
  24.     unsigned long long sum=0,index=0;
  25.     struct timeval tpstart,tpend;
  26.     float timeuse;
  27.     
  28.     test.a=0;
  29.     test.b=0;
  30.         
  31.     gettimeofday(&tpstart,NULL);
  32.     
  33.     for(sum=0;sum<APPLE_MAX_VALUE;sum++)
  34.     {
  35.         test.a+=sum;
  36.         test.b+=sum;
  37.     }
  38.     
  39.     sum=0;
  40.     for(index=0;index<ORANGE_MAX_VALUE;index++)
  41.     {
  42.         sum+=test1.a[index]+test1.b[index];
  43.     }
  44.     
  45.     gettimeofday(&tpend,NULL);
  46.     
  47.     timeuse=MSECOND*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;
  48.     timeuse/=MSECOND;
  49.     printf("main thread Used Time:%f\n",timeuse); 
  50.     
  51.   printf("a = %llu,b = %llu,sum=%llu\n",test.a,test.b,sum);
  52.     
  53.   return 0;
  54. }
dualrhread.c
  1. #include <sys/time.h>
  2. #include <pthread.h>


  3. #include<stdlib.h>
  4. #include<stdio.h>
  5. #include<sys/types.h>
  6. #include<sys/sysinfo.h>
  7. #include<unistd.h>

  8. #define __USE_GNU
  9. #include<sched.h>
  10. #include<ctype.h>
  11. #include<string.h> 

  12. #include <sys/syscall.h> /*此头必须带上*/

  13. pid_t gettid()
  14. {
  15.     return syscall(SYS_gettid);
  16. }


  17. #define ORANGE_MAX_VALUE 1000000
  18. #define APPLE_MAX_VALUE 100000000
  19. #define MSECOND 1000000

  20. struct apple
  21. {
  22.     unsigned long long a;
  23.     unsigned long long b;
  24. };

  25. struct orange
  26. {
  27.     int a[ORANGE_MAX_VALUE];
  28.     int b[ORANGE_MAX_VALUE];
  29. };

  30. struct apple test;
  31. struct orange test1;

  32. int cpu_nums;
  33. cpu_set_t mask;
  34. //cpu_set_t get;
  35.      
  36. inline int set_cpu(int i)
  37. {
  38.     __CPU_ZERO(&mask);
  39.     
  40.     if(<= cpu_nums)
  41.     {
  42.         __CPU_SET(i,&mask);
  43.         
  44.         if(-== sched_setaffinity(gettid(),sizeof(&mask),&mask))
  45.         {
  46.             return -1;
  47.         }
  48.     }
  49.     return 0;
  50. }

  51. void* add(void* x)
  52. {
  53.     int i;    
  54.     if(-== set_cpu(1))
  55.     {
  56.         return NULL;
  57.     } 
  58. /*    
  59.     __CPU_ZERO(&get);
  60.     if (sched_getaffinity(0, sizeof(get), &get) == -1)//获取线程CPU亲和力
  61.     {
  62.         printf("warning: cound not get thread affinity, continuing...\n");
  63.     }
  64.     
  65.     for (= 0; i < 2; i++)
  66.     {
  67.         if (__CPU_ISSET(i, &get))//判断线程与哪个CPU有亲和力
  68.         {
  69.             printf("this thread %d is running processor : %d\n", i,i);
  70.         }
  71.     }
  72. */ 
  73.     unsigned long long sum=0,index=0;    
  74.     for(sum=0;sum<APPLE_MAX_VALUE;sum++)
  75.     {
  76.         ((struct apple *)x)->+= sum;
  77.         ((struct apple *)x)->+= sum;    
  78.     }
  79.         
  80.     return NULL;
  81. }
  82.     
  83. int main () 
  84. {
  85.     
  86.     pthread_t ThreadA;
  87.     unsigned long long sum=0,index=0;
  88.     struct timeval tpstart,tpend;
  89.     float timeuse;
  90.     int i;
  91.     
  92.     test.a=0;
  93.     test.b=0;
  94.     
  95.     cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
  96.     
  97.     if(-== set_cpu(0))
  98.     {
  99.         return -1;
  100.     } 
  101.     
  102.     gettimeofday(&tpstart,NULL);
  103.         
  104.     pthread_create(&ThreadA,NULL,add,&test);
  105.     
  106. /*
  107.     __CPU_ZERO(&get);
  108.     if (sched_getaffinity(0, sizeof(get), &get) == -1)//获取线程CPU亲和力
  109.     {
  110.         printf("warning: cound not get thread affinity, continuing...\n");
  111.     }
  112.     
  113.     for (= 0; i < 2; i++)
  114.     {
  115.         if (__CPU_ISSET(i, &get))//判断线程与哪个CPU有亲和力
  116.         {
  117.             printf("this thread %d is running processor : %d\n", i,i);
  118.         }
  119.     }    
  120. */
  121.     for(index=0;index<ORANGE_MAX_VALUE;index++)
  122.     {
  123.         sum += test1.a[index]+test1.b[index];
  124.     }        
  125.    
  126.     pthread_join(ThreadA,NULL);

  127.     gettimeofday(&tpend,NULL);
  128.     
  129.     timeuse=MSECOND*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;
  130.     timeuse/=MSECOND;
  131.     printf("dual thread Used Time:%f\n",timeuse); 
  132.     
  133.     printf("a = %llu,b = %llu,sum=%llu\n",test.a,test.b,sum);
  134.     
  135.     return 0;
  136. }
threethread.c
  1. #include <sys/time.h>
  2. #include <pthread.h>

  3. #include<stdlib.h>
  4. #include<stdio.h>
  5. #include<sys/types.h>
  6. #include<sys/sysinfo.h>
  7. #include<unistd.h>

  8. #define __USE_GNU
  9. #include<sched.h>
  10. #include<ctype.h>
  11. #include<string.h> 

  12. #include <sys/syscall.h> /*此头必须带上*/

  13. pid_t gettid()
  14. {
  15.     return syscall(SYS_gettid); 
  16. }

  17. #define ORANGE_MAX_VALUE 1000000
  18. #define APPLE_MAX_VALUE 100000000
  19. #define MSECOND 1000000

  20. struct apple
  21. {
  22.     unsigned long long a;
  23.     //char c[128]; /*32,64,128*/
  24.     unsigned long long b;
  25.     //pthread_rwlock_t rwLock;
  26. };

  27. struct orange
  28. {
  29.     int a[ORANGE_MAX_VALUE];
  30.     int b[ORANGE_MAX_VALUE];
  31. };

  32. struct apple test;
  33. struct orange test1;

  34. int cpu_nums;
  35. cpu_set_t mask;
  36.      
  37. inline int set_cpu(int i)
  38. {
  39.     __CPU_ZERO(&mask);
  40.     
  41.     if(<= cpu_nums)
  42.     {
  43.         __CPU_SET(i,&mask);
  44.         
  45.         if(-== sched_setaffinity(gettid(),sizeof(&mask),&mask))
  46.         {
  47.             return -1;
  48.         }
  49.     }
  50.     return 0;
  51. }

  52. void* addx(void* x)
  53. {
  54. /*
  55.     if(-== set_cpu(0))
  56.     {
  57.         return NULL;
  58.     }
  59. */ 
  60.     unsigned long long sum=0,index=0;
  61.     //pthread_rwlock_wrlock(&((struct apple *)x)->rwLock);
  62.     for(sum=0;sum<APPLE_MAX_VALUE;sum++)
  63.     {
  64.         ((struct apple *)x)->+= sum;
  65.     }
  66.     //pthread_rwlock_unlock(&((struct apple *)x)->rwLock);
  67.     
  68.     return NULL;
  69. }

  70. void* addy(void* y)
  71. {
  72. /*  
  73.   if(-== set_cpu(1))
  74.     {
  75.         return NULL;
  76.     }
  77. */ 
  78.     unsigned long long sum=0,index=0;
  79.     //pthread_rwlock_wrlock(&((struct apple *)y)->rwLock);
  80.     for(sum=0;sum<APPLE_MAX_VALUE;sum++)
  81.     {
  82.         ((struct apple *)y)->+= sum;
  83.     }
  84.     //pthread_rwlock_unlock(&((struct apple *)y)->rwLock);
  85.     
  86.     return NULL;
  87. }

  88. int main () 
  89. {
  90.     pthread_t ThreadA,ThreadB;
  91.     unsigned long long sum=0,index=0;
  92.     struct timeval tpstart,tpend;
  93.     float timeuse;
  94. /*    
  95.     cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
  96.     
  97.     if(-== set_cpu(0))
  98.     {
  99.         return -1;
  100.     } 
  101.  */   
  102.     gettimeofday(&tpstart,NULL);
  103.     
  104.     pthread_create(&ThreadA,NULL,addx,&test);
  105.     pthread_create(&ThreadB,NULL,addy,&test);

  106.     for(index=0;index<ORANGE_MAX_VALUE;index++)
  107.     {
  108.         sum+=test1.a[index]+test1.b[index];
  109.     }
  110.     
  111.     pthread_join(ThreadA,NULL);
  112.     pthread_join(ThreadB,NULL);
  113.     
  114.     gettimeofday(&tpend,NULL);
  115.     
  116.     timeuse=MSECOND*(tpend.tv_sec-tpstart.tv_sec)+tpend.tv_usec-tpstart.tv_usec;
  117.     timeuse/=MSECOND;
  118.     printf("thread thread,Used Time:%f\n",timeuse); 
  119.     
  120.     printf("a = %llu,b = %llu,sum=%llu\n",test.a,test.b,sum);
  121.   
  122.     return 0;
  123. }
本文采用采用 gettimeofday() 来获取系统时钟(system clock)时间,可以精确到微秒。
本文平均值算法采用的是去掉一个最大值去掉一个最小值,然后平均

一、单核 单、多线程效率比较

单核

 

1

2

3

4

5

6

7

8

9

10

平均

单线程

0.951274

0.951883

0.951347

0.950893

0.810648

0.810705

0.951196

0.951343

0.811413

0.950892

0,916133

双线程

0.989287

0.986391

0.986576

0.985043

0.986335

0.985320

0.986790

0.986837

0.985602

0.986535

0.986298

三线程加锁

1.318721

1.318701

1.319036

1.318652

1.318614

1.318923

1.318636

1.318687

1.319026

1.318933

1.318785

三线程不加锁

1.318658

1.318766

1.318570

1.318894

1.318588

1.318658

1.318958

1.318709

1.318689

1.319020

1.318740



为什么多线程会比单线程更耗时呢?其原因就在于,线程启停以及线程上下文切换都会引起额外的开销,所以消耗的时间比单线程多。


二、双核---单线程、多线程、优化后多线程效率比较

双核三线程不加锁

 

1

2

3

4

5

6

7

8

9

10

Best

单线程

0.947490

0.948153

0.950648

0.950412

0.950422

0.950407

0.810345

0.950386

0.947501

0.947513

0.949036

 

双线程

0.983432

0.980912

0.976860

0.975621

0.976582

0.978240

0.975736

0.981523

0.979724

0.981337

0.978864

 

三线程加锁

1.310762

1.309590

1.308914

1.309287

1.310924

1.310837

1.307147

1.311698

1.311861

1.309007

1.310127

三线程不加锁

1.431863

1.422951

1.472916

1.482321

1.477823

1.477272

1.423854

1.423412

1.422267

1.423243

1.444167

三线程加锁Cache32

1.310174

1.308985

1.311462

1.311574

1.309590

1.311329

1.311602

1.311154

1.307573

1.310447

1.310589

三线程不加锁Cache32

0.664234

0.664244

0.664186

0.664264

0.664317

0.664234

0.662658

0.662176

0.662164

0.662159

0.663520


为什么三线程不加锁比三线程加锁还慢呢?
当两个线程写入同一个 cache 的不同部分时,会互相竞争该 cache 行,也就是写后写的问题。下文会详细分析。
为什么三线程加锁Cache32比三线程不加锁Cache32比慢几乎一倍呢?
其原因也很简单,那把读写锁就是罪魁祸首。实际情况并不是并行执行,反而成了串行执行。

三、针对 Cache 的优化

在串行程序设计过程中,为了节约带宽或者存储空间,比较直接的方法,就是对数据结构做一些针对性的设计,将数据压缩 (pack) 的更紧凑,减少数据的移动,以此来提高程序的性能。但在多核多线程程序中,这种方法往往有时会适得其反。

数据不仅在执行核和存储器之间移动,还会在执行核之间传输。根据数据相关性,其中有两种读写模式会涉及到数据的移动:写后读和写后写 ,因为这两种模式会引发数据的竞争,表面上是并行执行,但实际只能串行执行,进而影响到性能。

处理器交换的最小单元是 cache 行,或称 cache 块。在多核体系中,对于不共享 cache 的架构来说,两个独立的 cache 在需要读取同一 cache 行时,会共享该 cache 行,如果在其中一个 cache 中,该 cache 行被写入,而在另一个 cache 中该 cache 行被读取,那么即使读写的地址不相交,也需要在这两个 cache 之间移动数据,这就被称为 cache 伪共享,导致执行核必须在存储总线上来回传递这个 cache 行,这种现象被称为“乒乓效应”。

同样地,当两个线程写入同一个 cache 的不同部分时,也会互相竞争该 cache 行,也就是写后写的问题。上文曾提到,不加锁的方案反而比加锁的方案更慢,就是互相竞争 cache 的原因。

在 X86 机器上,某些处理器的一个 cache 行是64字节,具体可以参看 Intel 的参考手册。

既然不加锁三线程方案的瓶颈在于 cache,那么让 apple 的两个成员 a 和 b 位于不同的 cache 行中,效率会有所提高吗?

修改后的代码片断如下:

  1. struct apple
  2. {
  3.     unsigned long long a;
  4.     char c[128]; /*32,64,128*/
  5.     unsigned long long b;
  6. };
小小的一行代码,尽然带来了如此高的收益,不难看出,我们是用空间来换时间。当然读者也可以采用更简便的方法: __attribute__((__aligned__(L1_CACHE_BYTES))) 来确定 cache 的大小。

参考http://www.ibm.com/developerworks/cn/linux/l-cn-optimization/index.html

转自:http://blog.chinaunix.net/uid-20556054-id-3070826.html

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理