2015-12-02 64 views
3

我正在CentOS 7服务器上测试带有gcc(GCC)4.8.3 20140911的Linux上的pthread并行代码。在Linux上GCC/pthread并行代码比简单的单线程代码慢得多

单线程版本是简单的,它是用来初始化一个10000 * 10000矩阵:

int main(int argc) 
{ 
    int size = 10000; 

    int * r = (int*)malloc(size * size * sizeof(int)); 
    for (int i=0; i<size; i++) { 
      for (int j=0; j<size; j++) { 
       r[i * size + j] = rand(); 
      } 
    } 
    free(r); 
} 

然后我想看看并行代码可以提高性能:

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 

int size = 10000; 

void *SetOdd(void *param) 
{ 
    printf("Enter odd\n"); 
    int * r  = (int*)param; 
    for (int i=0; i<size; i+=2) { 
     for (int j=0; j<size; j++) { 
       r[i * size + j] = rand(); 
     } 
    } 
    printf("Exit Odd\n"); 
    pthread_exit(NULL); 
    return 0; 
} 

void *SetEven(void *param) 
{ 
    printf("Enter Even\n"); 
    int * r  = (int*)param; 
    for (int i=1; i<size; i+=2) { 
     for (int j=0; j<size; j++) { 
       r[i * size + j] = rand(); 
     } 
    } 
    printf("Exit Even\n"); 
    pthread_exit(NULL); 
    return 0; 
} 

int main(int argc) 
{ 
    printf("running in thread\n"); 
    pthread_t threads[2]; 
    int * r = (int*)malloc(size * size * sizeof(int)); 
    int rc0 = pthread_create(&threads[0], NULL, SetOdd, (void *)r); 
    int rc1 = pthread_create(&threads[1], NULL, SetEven, (void *)r); 
    for(int t=0; t<2; t++) { 
      void* status; 
      int rc = pthread_join(threads[t], &status); 
      if (rc) { 
       printf("ERROR; return code from pthread_join() is %d\n", rc); 
       exit(-1); 
      } 
      printf("Completed join with thread %d status= %ld\n",t,  (long)status); 
     } 

    free(r); 
    return 0; 
} 

的简单的代码运行约0.8秒,而多线程版本运行约10秒!!!!!!!

我在4核心服务器上运行。但为什么多线程版本太慢?

+0

该代码可能会阻止'rand()'中的互斥量,因为它保证了产生的数字的一定序列。您需要学习使用分析器(例如gprof)来真正识别瓶颈。 – Dummy00001

+1

'valgrind --tool = callgrind'和'gprof'(在应用程序的静态版本上)都清楚地显示了瓶颈所在。它确实在'rand()'中。干杯。 – Dummy00001

回答

5

rand()既不是线程安全的也不是可重入的。所以你不能在多线程应用程序中使用rand()

改为使用rand_r(),它也是一个伪随机生成器,并且是线程安全的。如果你在乎。使用rand_r()可以缩短我的系统上2个内核的代码执行时间(大约是单线程版本的一半)。

在您的两个线程功能,这样做:

void *SetOdd(void *param) 
{ 
    printf("Enter odd\n"); 
    unsigned int s = (unsigned int)time(0); 

    int * r  = (int*)param; 
    for (int i=0; i<size; i+=2) { 
     for (int j=0; j<size; j++) { 
       r[i * size + j] = rand_r(&s); 
     } 
    } 
    printf("Exit Odd\n"); 
    pthread_exit(NULL); 
    return 0; 
} 

更新:

虽然C和POSIX标准并强制rand()是线程安全的功能,glibc实施(在Linux上使用)确实以线程安全的方式实现它。

如果我们看一下glibc implementation of the rand(), 有一个锁:

291 __libc_lock_lock (lock); 
292 
293 (void) __random_r (&unsafe_state, &retval); 
294 
295 __libc_lock_unlock (lock); 
296 

任何同步结构(互斥,条件变量等)是坏的表现,即在代码中使用这种结构的最少次数越多越好这是为了性能(当然,我们无法完全避免在多线程应用程序中确定它们)。

所以只有一个线程可以实际访问随机数生成器,因为两个线程都在为锁一直争夺。这就解释了为什么rand()会导致多线程代码的糟糕表现。

+0

在多线程应用程序中使用'time()'作为随机种子是不可取的。添加线程是('pthread_self()')或堆栈变量的地址,或者使用'clock_gettime()'中的非秒部分来随机化它。 – Dummy00001

+0

我同意它可能不会产生预期的结果。我用它作为线程安全问题的“廉价”替代品。我稍后会更新答案。如果随机数的质量很重要,那么可以使用'drand48_r()'和朋友。 'pthread_self()'可能不是一个好主意,因为'pthread_t'是一个不透明类型,不能依赖某种整数表示。至少在Linux上可以使用'gettid()'来代替同样的效果。 –

+0

这仅仅是一个简单的评论,表明代码在生产中不可靠。因此,多线程初学者不会有任何愚蠢的想法;) – Dummy00001

3

rand()功能被设计用于产生可预测的随机数序列(并且序列的种子可以由srand()函数控制)。这意味着该函数具有内部状态,很可能受互斥锁保护。

锁的存在可以通过使用例如, gprofvalgrind --tool=callgrind工具。 (对于gprof来检测与标准库有关的问题,您需要编译/链接应用程序-static。)

在单线程模式下,互斥锁处于非活动状态。但是在多线程模式下,互斥体会导致线程永久性的冲突和停顿,两者都会在紧密的循环中争夺相同的锁。这严重降低了多线程性能。