以下程序与here描述的程序基本相同。当我使用两个线程(NTHREADS == 2)运行并编译程序时,我得到以下运行时间:
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意味着在多线程应用程序中使用。
答案 0 :(得分: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]);
}
使我的双核机器的运行时间大大加快。
这将证实它本来要测试的怀疑 - 你是在两个独立的线程中改变同一缓存行上的值,因此有缓存争用。 Herb Sutter的'machine architecture - what your programming language never told you' talk值得一看,如果你有时间,如果你还不知道,他会在1:20左右开始虚假分享。
计算出你的缓存行大小,并创建每个线程的数据,使其与之对齐。
将所有线程的数据压缩到结构中更加清晰,并对齐:
#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字节对齐。
(我将ITERATIONS减少了十倍而不是一台愚蠢的快速机器)
答案 1 :(得分:1)
我不知道这是否相关 - 但我只是看到一个非常相似的行为(2个线程比一个线程慢一个数量级)...我基本上改变了一个:
srand(seed);
foo = rand();
到
myseed = seed;
foo = rand_r(&myseed);
并且“固定”它(2个线程现在可靠地几乎快两倍 - 例如19s而不是35s)。
我不知道问题是什么 - 锁定或缓存rand()
内部的一致性?无论如何,还有一个random_r()
所以也许对你(一年前)或其他人有用。