2011-11-30 81 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毫秒