2010-06-08 108 views
10

以下程序与here中描述的程序基本相同。当我运行和编译使用两个线程(来确定nthreads == 2),我得到以下运行时间程序:多线程random_r比单线程版本更慢

real  0m14.120s 
user  0m25.570s 
sys   0m0.050s 

当它运行只有一个线程(来确定nthreads == 1),我得到的运行时间即使只使用一个核心也会明显更好。

real  0m4.705s 
user  0m4.660s 
sys   0m0.010s 

我的系统是双核心的,我知道random_r是线程安全的,我非常肯定它是非阻塞的。当同样的程序在没有random_r的情况下运行并且使用余弦和正弦的计算作为替换时,双线程版本的运行时间大约是预期的1/2。

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

#define NTHREADS 2 
#define PRNG_BUFSZ 8 
#define ITERATIONS 1000000000 

void* thread_run(void* arg) { 
    int r1, i, totalIterations = ITERATIONS/NTHREADS; 
    for (i = 0; i < totalIterations; i++){ 
     random_r((struct random_data*)arg, &r1); 
    } 
    printf("%i\n", r1); 
} 

int main(int argc, char** argv) { 
    struct random_data* rand_states = (struct random_data*)calloc(NTHREADS, sizeof(struct random_data)); 
    char* rand_statebufs = (char*)calloc(NTHREADS, PRNG_BUFSZ); 
    pthread_t* thread_ids; 
    int t = 0; 
    thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t)); 
    /* create threads */ 
    for (t = 0; t < NTHREADS; t++) { 
     initstate_r(random(), &rand_statebufs[t], PRNG_BUFSZ, &rand_states[t]); 
     pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t]); 
    } 
    for (t = 0; t < NTHREADS; t++) { 
     pthread_join(thread_ids[t], NULL); 
    } 
    free(thread_ids); 
    free(rand_states); 
    free(rand_statebufs); 
} 

我很困惑,为什么产生随机数当两个线程版本的性能比单线程版本差多少,考虑random_r是指在多线程应用中使用。

回答

13

一个非常简单的改变空间中的数据进行存储:

struct random_data* rand_states = (struct random_data*)calloc(NTHREADS * 64, sizeof(struct random_data)); 
char* rand_statebufs = (char*)calloc(NTHREADS*64, PRNG_BUFSZ); 
pthread_t* thread_ids; 
int t = 0; 
thread_ids = (pthread_t*)calloc(NTHREADS, sizeof(pthread_t)); 
/* create threads */ 
for (t = 0; t < NTHREADS; t++) { 
    initstate_r(random(), &rand_statebufs[t*64], PRNG_BUFSZ, &rand_states[t*64]); 
    pthread_create(&thread_ids[t], NULL, &thread_run, &rand_states[t*64]); 
} 

结果在我的双核机器上运行得更快的时间。

这将确认它是否意味着要测试的怀疑 - 您是在两个单独的线程中的同一缓存线上对值进行变异,因此存在缓存争用。草药萨特的'machine architecture - what your programming language never told you' talk值得关注,如果你有时间,如果你不知道这一点,但他展示了从1:20左右开始的虚假分享。

计算出你的缓存行大小,并创建每个线程的数据,使它与它对齐。

这是一个有点清洁剂的所有线程的数据plonk的成结构和排列是:

#define CACHE_LINE_SIZE 64 

struct thread_data { 
    struct random_data random_data; 
    char statebuf[PRNG_BUFSZ]; 
    char padding[CACHE_LINE_SIZE - sizeof (struct random_data)-PRNG_BUFSZ]; 
}; 

int main (int argc, char** argv) 
{ 
    printf ("%zd\n", sizeof (struct thread_data)); 

    void* apointer; 

    if (posix_memalign (&apointer, sizeof (struct thread_data), NTHREADS * sizeof (struct thread_data))) 
     exit (1); 

    struct thread_data* thread_states = apointer; 

    memset (apointer, 0, NTHREADS * sizeof (struct thread_data)); 

    pthread_t* thread_ids; 

    int t = 0; 

    thread_ids = (pthread_t*) calloc (NTHREADS, sizeof (pthread_t)); 

    /* create threads */ 
    for (t = 0; t < NTHREADS; t++) { 
     initstate_r (random(), thread_states[t].statebuf, PRNG_BUFSZ, &thread_states[t].random_data); 
     pthread_create (&thread_ids[t], NULL, &thread_run, &thread_states[t].random_data); 
    } 

    for (t = 0; t < NTHREADS; t++) { 
     pthread_join (thread_ids[t], NULL); 
    } 

    free (thread_ids); 
    free (thread_states); 
} 

CACHE_LINE_SIZE 64:

refugio:$ gcc -O3 -o bin/nixuz_random_r src/nixuz_random_r.c -lpthread 
refugio:$ time bin/nixuz_random_r 
64 
63499495 
944240966 

real 0m1.278s 
user 0m2.540s 
sys 0m0.000s 

或者你也可以使用双高速缓存行的大小,并使用malloc - 额外的填充确保变异的内存在不同的行上,malloc是16(IIRC)而不是64字节对齐。

(我以10的因数降低ITERATIONS而不是一个愚蠢快速机)

+0

呃。这可以咬住几乎任何小的,密集的结构,多线程将尝试写入部分,对吗? – 2010-06-08 20:03:28

+0

非常感谢您的帮助,我绝对不会想到这一点。 Ps。我将rand_states和rand_statebufs移入线程,并从那里初始化随机数生成器。这也很好地解决了缓存问题。 – Nixuz 2010-06-08 20:06:38

+3

@尼古拉斯:是的。记住不要过分吝啬。请注意,将线程本地分配放在一起也可以提供帮助。由于可以避免高速缓存争用和锁定,因此线程本地机器人可以是一个巨大的胜利。 – 2010-06-08 20:13:34

1

我不知道这是否是相关或不 - 但我只是看到了一个非常类似的行为(量级慢比具有一个2个线程)...我基本上改变了:

srand(seed); 
    foo = rand(); 

myseed = seed; 
    foo = rand_r(&myseed); 

和“固定”它(2个线程是现在可以可靠地几乎两倍快 - 例如,1 9s而不是35s)。

我不知道这个问题可能是什么 - 在内部锁定或缓存一致性rand()也许?无论如何,也有一个random_r()所以也许这对你(一年前)或其他人有用。