我有一个小的C程序,它使用monte-carlo -simulation计算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 < iparam->rounds; ++i) {
x = (float)rand()/RAND_MAX;
y = (float)rand()/RAND_MAX;
z = x * x + y * y;
if (z <= 1.f) /* circle radius of 1 */
++hits;
}
iparam->hits = (long*)hits;
return NULL;
}
现在我有一个奇怪的发现。在具有相同样本集n和线程数量i的情况下,此程序将花费更多的时间而不是更少的时间。
以下是一些平均运行时间(可重现):
-------------------------------------------------
| 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()
正在序列化所有线程。(*)
确认是否存在问题的一个简单技巧是,在调试器下启动程序,然后执行几次:暂停它,捕获线程的堆栈跟踪,然后继续。无论在堆栈跟踪中最常出现的是什么,都是瓶颈。
(*)使锁争用导致额外的性能损失的事实使其变得更加缓慢。同样,许多线程增加了进程调度和上下文切换的额外开销。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句