2015-12-02 13 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。)

在單線程模式下,互斥鎖處於非活動狀態。但是在多線程模式下,互斥體會導致線程永久性的衝突和停頓,兩者都會在緊密的循環中爭奪相同的鎖。這嚴重降低了多線程性能。

相關問題