如何并行生成随机数?

问题描述 投票:14回答:6

我想使用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。:我还没有接受任何答案,因为我需要确定这个想法是好的。

c random openmp
6个回答
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倍加速的阴影。


8
投票

你不能在多个线程中使用C rand()函数;这会导致未定义的行为。某些实现可能会给你锁定(这将使它变慢);其他人可能会允许线程破坏彼此的状态,可能会导致程序崩溃或只是给出“坏”的随机数。

要解决此问题,请编写自己的PRNG实现或使用允许调用者存储并将状态传递给PRNG迭代器函数的现有实现。


6
投票

获取每个线程根据其线程ID设置不同的种子,例如srand(omp_get_thread_num() * 1000);


4
投票

似乎rand在Linux上的所有线程和Windows上的线程本地存储状态之间具有全局共享状态。由于必要的同步,Linux上的共享状态导致您的速度减慢。

我不认为C库中有一种可移植的方式在多个线程上使用RNG并行,所以你需要另一个。你可以使用Mersenne Twister。正如marcog所说,你需要以不同方式初始化每个线程的种子。


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个不同的伪随机数,没有任何互斥锁定或竞争条件。


1
投票

随机数可以非常快地生成,因此通常内存将成为瓶颈。通过在多个线程之间划分此任务,您可以创建额外的通信和同步开销(并且不同内核的高速缓存的同步并不便宜)。

使用具有更好的random()函数的单个线程会更好。

© www.soinside.com 2019 - 2024. All rights reserved.