我想使用openMP并行生成伪随机数,如下所示:
int i;
#pragma omp parallel for
for (i=0;i<100;i++)
{
printf("%d %d %d\n",i,omp_get_thread_num(),rand());
}
return 0;
我在Windows上测试了它并且我获得了巨大的加速,但是每个线程生成了完全相同的数字。我已经在Linux上测试了它并且我得到了巨大的减速,8核处理器上的并行版本比顺序慢了大约10倍,但是每个线程生成了不同的数字。
有没有办法同时拥有加速和不同的数字?
编辑27.11.2010
我想我已经用Jonathan Dursi的帖子解决了这个问题。似乎下面的代码在linux和windows上运行得很快。数字也是伪随机的。你怎么看待它?
int seed[10];
int main(int argc, char **argv)
{
int i,s;
for (i=0;i<10;i++)
seed[i] = rand();
#pragma omp parallel private(s)
{
s = seed[omp_get_thread_num()];
#pragma omp for
for (i=0;i<1000;i++)
{
printf("%d %d %d\n",i,omp_get_thread_num(),s);
s=(s*17931+7391); // those numbers should be choosen more carefully
}
seed[omp_get_thread_num()] = s;
}
return 0;
}
PS:我还没有接受任何答案,因为我需要确定这个想法是好的。
答案 0 :(得分:9)
我会在这里发布我发布到Concurrent random number generation的内容:
我认为你正在寻找rand_r(),它明确地将当前的RNG状态作为参数。然后每个线程都应该拥有它自己的种子数据副本(无论你是希望每个线程都使用相同的种子开始,还是不同的,取决于你正在做什么,这里你希望它们不同,或者你得到同一行一次又一次)。这里有一些关于rand_r()和线程安全的讨论:whether rand_r is real thread safe?。
所以说你希望每个线程都有它的种子从它的线程号开始(这可能不是你想要的,因为每次你运行相同数量的线程时它会给出相同的结果,但就像一个示例):
#pragma omp parallel default(none)
{
int i;
unsigned int myseed = omp_get_thread_num();
#pragma omp for
for(i=0; i<100; i++)
printf("%d %d %d\n",i,omp_get_thread_num(),rand_r(&myseed));
}
编辑:只是在云雀上,检查以上是否会获得任何加速。完整的代码是
#define NRANDS 1000000
int main(int argc, char **argv) {
struct timeval t;
int a[NRANDS];
tick(&t);
#pragma omp parallel default(none) shared(a)
{
int i;
unsigned int myseed = omp_get_thread_num();
#pragma omp for
for(i=0; i<NRANDS; i++)
a[i] = rand_r(&myseed);
}
double sum = 0.;
double time=tock(&t);
for (long int i=0; i<NRANDS; i++) {
sum += a[i];
}
printf("Time = %lf, sum = %lf\n", time, sum);
return 0;
}
其中tick和tock只是gettimeofday()
的包装器,而tock()以秒为单位返回差异。打印Sum只是为了确保没有任何优化,并展示一个小点;你会得到不同数量的线程,因为每个线程都有自己的threadnum作为种子;如果您使用相同数量的线程一次又一次地运行相同的代码,您将获得相同的总和,原因相同。无论如何,计时(在没有其他用户的8核nehalem盒子上运行):
$ export OMP_NUM_THREADS=1
$ ./rand
Time = 0.008639, sum = 1074808568711883.000000
$ export OMP_NUM_THREADS=2
$ ./rand
Time = 0.006274, sum = 1074093295878604.000000
$ export OMP_NUM_THREADS=4
$ ./rand
Time = 0.005335, sum = 1073422298606608.000000
$ export OMP_NUM_THREADS=8
$ ./rand
Time = 0.004163, sum = 1073971133482410.000000
如此加速,即使不是很好;正如@ruslik指出的那样,这不是一个计算密集型的过程,而其他问题如内存带宽也开始发挥作用。因此,在8个核心上只有2倍速的加速。
答案 1 :(得分:8)
您无法使用多个线程中的C rand()
函数;这会导致未定义的行为。某些实现可能会给你锁定(这将使它变慢);其他人可能允许线程破坏对方的状态,可能会导致程序崩溃或只是给出“坏”的随机数。
要解决此问题,请编写自己的PRNG实现或使用允许调用者存储并将状态传递给PRNG迭代器函数的现有实现。
答案 2 :(得分:6)
让每个线程根据其线程ID设置不同的种子,例如srand(omp_get_thread_num() * 1000)
;
答案 3 :(得分:4)
似乎rand
在Linux上的所有线程与Windows上的线程本地存储状态之间具有全局共享状态。由于必要的同步,Linux上的共享状态导致您的速度减慢。
我不认为C库中有一种可移植的方式在多个线程上使用RNG并行,所以你需要另一个。您可以使用Mersenne Twister。正如marcog所说,你需要以不同的方式初始化每个线程的种子。
答案 4 :(得分:2)
在linux / unix上你可以使用
long jrand48(unsigned short xsubi[3]);
其中xsubi [3]编码随机数生成器的状态,如下所示:
#include<stdio.h>
#include<stdlib.h>
#include <algorithm>
int main() {
unsigned short *xsub;
#pragma omp parallel private(xsub)
{
xsub = new unsigned short[3];
xsub[0]=xsub[1]=xsub[2]= 3+omp_get_thread_num();
int j;
#pragma omp for
for(j=0;j<10;j++)
printf("%d [%d] %ld\n", j, omp_get_thread_num(), jrand48(xsub));
}
}
用
编译g++-mp-4.4 -Wall -Wextra -O2 -march=native -fopenmp -D_GLIBCXX_PARALLEL jrand.cc -o jrand
(将g ++ - mp-4.4替换为您需要调用g ++版本4.4或4.3的任何内容) 你得到了
$ ./jrand
0 [0] 1344229389
1 [0] 1845350537
2 [0] 229759373
3 [0] 1219688060
4 [0] -553792943
5 [1] 360650087
6 [1] -404254894
7 [1] 1678400333
8 [1] 1373359290
9 [1] 171280263
即。 10个不同的伪随机数,没有任何互斥锁定或竞争条件。
答案 5 :(得分:1)
随机数可以非常快地生成,因此通常内存会成为瓶颈。通过在多个线程之间划分此任务,您可以创建额外的通信和同步开销(并且不同内核的高速缓存的同步并不便宜)。
最好使用具有更好random()
功能的单个线程。