2011-11-30 80 views
7

我有以下的任務來證明假共享,寫了一個簡單的程序:假共享和並行線程

#include <sys/times.h> 
#include <time.h> 
#include <stdio.h> 
#include <pthread.h> 

long long int tmsBegin1,tmsEnd1,tmsBegin2,tmsEnd2,tmsBegin3,tmsEnd3; 

int array[100]; 

void *heavy_loop(void *param) { 
    int index = *((int*)param); 
    int i; 
    for (i = 0; i < 100000000; i++) 
    array[index]+=3; 
} 

int main(int argc, char *argv[]) { 
    int  first_elem = 0; 
    int  bad_elem = 1; 
    int  good_elem = 32; 
    long long time1; 
    long long time2; 
    long long time3; 
    pthread_t  thread_1; 
    pthread_t  thread_2; 

    tmsBegin3 = clock(); 
    heavy_loop((void*)&first_elem); 
    heavy_loop((void*)&bad_elem); 
    tmsEnd3 = clock(); 

    tmsBegin1 = clock(); 
    pthread_create(&thread_1, NULL, heavy_loop, (void*)&first_elem); 
    pthread_create(&thread_2, NULL, heavy_loop, (void*)&bad_elem); 
    pthread_join(thread_1, NULL); 
    pthread_join(thread_2, NULL); 
    tmsEnd1 = clock(); 

    tmsBegin2 = clock(); 
    pthread_create(&thread_1, NULL, heavy_loop, (void*)&first_elem); 
    pthread_create(&thread_2, NULL, heavy_loop, (void*)&good_elem); 
    pthread_join(thread_1, NULL); 
    pthread_join(thread_2, NULL); 
    tmsEnd2 = clock(); 

    printf("%d %d %d\n", array[first_elem],array[bad_elem],array[good_elem]); 
    time1 = (tmsEnd1-tmsBegin1)*1000/CLOCKS_PER_SEC; 
    time2 = (tmsEnd2-tmsBegin2)*1000/CLOCKS_PER_SEC; 
    time3 = (tmsEnd3-tmsBegin3)*1000/CLOCKS_PER_SEC; 
    printf("%lld ms\n", time1); 
    printf("%lld ms\n", time2); 
    printf("%lld ms\n", time3); 

    return 0; 
} 

當我看到結果我感到非常驚訝(我在我的酷睿i5-430M處理器,運行它)。

  • 由於虛假分享,這是1020毫秒。
  • 沒有虛假分享,它是710毫秒,只有30%而不是300%(它寫在一些網站上,它會比300-400%更快)。
  • 沒有使用pthreads,它是580毫秒。

請給我看看我的錯誤或解釋它爲什麼會發生。

回答

18

虛假共享是多個核心單獨緩存訪問同一物理內存區域(儘管不是相同的地址 - 這將是真正的共享)。

要理解錯誤共享,您需要了解緩存。在大多數處理器中,每個內核都有自己的L1緩存,它保存最近訪問過的數據。緩存以「行」組織,這些行是對齊的數據塊,通常爲32或64字節(取決於您的處理器)。當您從不在緩存中的地址讀取時,整行將從主內存(或L2緩存)讀入L1。當您寫入緩存中的地址時,包含該地址的行被標記爲「髒」。

以下是共享方面的內容。如果多個內核正在從同一行讀取數據,則它們每個都可以在L1中有一行副本。但是,如果副本標記爲髒,則會使其他緩存中的行無效。如果這種情況沒有發生,那麼在其他內核上寫入的內容可能不會被其他內核看到,直到很久以後。所以下一次其他內核會從該行讀取數據時,緩存會丟失,並且必須重新獲取該行。

False當內核正在讀寫同一行上的不同地址時,將發生共享。儘管他們沒有共享數據,但由於他們非常密切,緩存就像他們一樣。

此效果高度依賴於處理器的體系結構。如果你有一個核心處理器,你根本看不到這個效果,因爲不會共享。如果你的緩存行數較長,你會在「壞」和「好」情況下看到效果,因爲它們仍然靠得很近。如果你的內核不共享一個二級緩存(我猜他們是這樣做的),你可能會看到300-400%的差異,因爲他們必須一直到緩存未命中的主內存。

您可能還想知道,每個線程都是讀寫都是重要的(+ =而不是=)。某些處理器具有直寫型高速緩存,這意味着如果內核寫入不在高速緩存中的地址,則不會錯過並從內存中獲取該行。將其與回寫緩存進行對比,該緩存在寫入時沒有錯過。

+0

我覺得數組[0]和陣列[1]應該在一個緩存行中。他們非常接近,不是嗎? –

+0

@AlexeyMatveev:31日和32日也非常接近。但是你認爲它們屬於不同的緩存行。事實是,他們可能也可能不在同一個緩存行上。如果1到5(以及1之前的所有值都適用)會進入一個緩存行,並且6個槽37會進入另一個緩存行? – 2011-11-30 19:27:34

+0

@ Vlad-Lazarenko,我明白了,我也用另一個數字來測試它。 –

2

我使用了C語言的clock()函數,它給出了從開始到結束所經過的CPU時鐘數。現在,當您運行兩個並行線程時,CPU週期數將爲(CPU1 +時鐘週期cpu2的週期)。我想你想要的是一個真正的計時器時鐘。爲此,您使用clock_gettime(),您將獲得預期的輸出。

我用clock_gettime()運行了你的代碼。我得到這個:

與假共享874.587381毫秒

不假共享331.844278毫秒

連續計算604.160276毫秒