将工作分成更multithreading需要更多时间,为什么?

我有一个小的C程序,它使用monte-carlo模拟计算pi ,它基本上只测试一个随机点[x,y],如果它在圆圈内部或外部。

为了近似pi,我必须使用大量的样本n ,其具有O(n)的直接比例复杂度。 因此,尝试计算大量样本n,我实现了POSIX线程 api以平衡计算能力。

我的代码如下所示:

pthread_t worker[nthreads]; /* creates workers for each thread */ struct param aparam[nthreads]; /* struct param{ long* hits; long rounds; }; */ long nrounds = nsamples / nthreads; /* divide samples to subsets of equal rounds per thread */ for (int i = 0; i < nthreads; ++i) { /* loop to create threads */ aparam[i].hits = 0; aparam[i].rounds = nrounds; pthread_create(&worker[i], NULL, calc_pi, &aparam[i]); /* calls calc_pi(void* vparam){} */ } long nhits = 0; for (int j = 0; j < nthreads; ++j) { /* collects results */ pthread_join(worker[j], NULL); nhits += (long)aparam[j].hits; /* counts hits inside the cicrle */ } 

这就是每个线程正在做的事情:

 void* calc_pi(void* vparam) { /* counts hits inside a circle */ struct param *iparam; iparam = (struct param *) vparam; long hits = 0; float x, y, z; for (long i = 0; i rounds; ++i) { x = (float)rand()/RAND_MAX; y = (float)rand()/RAND_MAX; z = x * x + y * y; if (z hits = (long*)hits; return NULL; } 

现在我有一个奇怪的观察。 使用相同的样本集n和越来越多的线程, 我的程序需要更多的时间而不是更少的时间

以下是一些平均运行时间(可重复):

 ------------------------------------------------- | Threads[1] | Samples[1] | Rounds[1] | Time[s] | ------------------------------------------------- | 32 | 268435456 | 8388608 | 118 | | 16 | 268435456 | 16777216 | 106 | | 8 | 268435456 | 33554432 | 125 | | 4 | 268435456 | 67108864 | 152 | | 2 | 268435456 | 134217728 | 36 | | 1 | 268435456 | 268435456 | 15 | ------------------------------------------------- 

例如,为什么两个线程执行相同的工作所花费的时间比单个线程多两倍? 我的假设是划分工作的两个线程应该将时间减少至少50%。

使用GCC 4.9.1和以下标志编译:

 gcc -O2 -std=gnu11 -pthread pipa.c -lpthread -o pipa 

我的硬件是双Intel Xeon E5520(2个处理器,每个4核)@ 2.26 GHz,禁用超线程,运行2.6.18内核的科学linux。

有任何想法吗?

线程执行的最昂贵的操作是调用rand()rand()是一个天真的,简单的,通常是非MT可伸缩的函数(因为它保证相同的种子产生相同的随机数序列)。 我认为rand()的锁是序列化所有线程。(*)

确认是否存在问题的一个简单技巧是在调试器下启动程序,然后多次:暂停它,捕获线程的堆栈跟踪,继续。 无论堆栈中最常出现的是什么,很可能是瓶颈。

(*)使得它更慢的原因是锁争用会导致额外的性能损失。 此外,许multithreading增加了进程调度和上下文切换的额外开销。