虚假分享和pthreads

我有以下任务来演示虚假共享并编写了一个简单的程序:

#include  #include  #include  #include  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毫秒。

请告诉我我的错误或解释为什么会发生。

虚假共享是多个内核的结果,其中单独的高速缓存访​​问物理内存的相同区域(尽管不是相同的地址 – 这将是真正的共享)。

要了解错误共享,您需要了解缓存。 在大多数处理器中,每个核心都有自己的L1缓存,它保存最近访问的数据。 高速缓存以“行”组织,这些行是对齐的数据块,通常为32或64字节长度(取决于您的处理器)。 当您从不在高速缓存中的地址读取时,整行将从主存储器(或L2高速缓存)读入L1。 当您写入缓存中的地址时,包含该地址的行标记为“脏”。

这是共享方面的用武之地。如果多个核心从同一行读取,则每个核心都可以在L1中拥有该行的副本。 但是,如果副本标记为脏,则会使其他高速缓存中的行无效。 如果没有发生这种情况,那么在一个核心上进行的写入可能在很久以后才会被其他核心看到。 因此,下次另一个核心从该行读取时,缓存未命中,并且必须再次获取该行。

当核心正在读取和写入同一行上的不同地址时,会发生错误共享。 即使它们不共享数据,缓存也会像它们一样,因为它们非常接近。

此效果高度依赖于处理器的体系结构。 如果你有一个核心处理器,你根本看不到效果,因为没有共享。 如果您的缓存行更长,您会在“坏”和“好”情况下看到效果,因为它们仍然靠近在一起。 如果您的内核没有共享L2缓存(我猜他们会这样做),您可能会看到300-400%的差异,因为他们必须在缓存未命中时一直到主内存。

您可能还想知道每个线程都在读写(+ =而不是=)是很重要的。 某些处理器具有直写高速缓存,这意味着如果核心写入不在高速缓存中的地址,则它不会错过并从内存中获取该行。 与回写缓存相比,后者确实错过了写入。

我用谷歌搜索了C.中的clock()函数。它给出了从开始到结束所经过的CPU时钟数。当你运行两个并行线程时,CPU周期数将是(CPU1的时钟周期+ cpu2的时钟周期) 。 我想你想要的是一个真正的计时器时钟。为此你使用clock_gettime(),你将得到预期的输出。

我使用clock_gettime()运行你的代码。我得到了这个:

与错误共享874.587381毫秒

没有错误共享331.844278 ms

顺序计算604.160276 ms