如何使用 OpenMP 在 C 代码中生成 0 和 1 之间均匀分布的随机数?

How to generate uniformly distributed random numbers between 0 and 1 in a C code using OpenMP?

我正在尝试编写一个 OpenMP 代码,其中每个线程将处理 0 到 1 之间均匀分布的随机数的大数组。每个线程都需要具有不同且独立的随​​机数分布。此外,每次调用代码时,随机数分布都需要不同。这就是我现在正在使用的。这是否总能保证每个线程都有其 own/different 随机数序列?每次调用代码时序列都会不同吗?这样做的正确方法是什么?下面的代码让每个线程生成 5 个样本,但在实际中 运行 它将是数百万个。

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <time.h>

int main(int argc, char* argv[])
{
    int numthreads,i;
    #pragma omp parallel private(i)
    {
        int id;
        id=omp_get_thread_num();
        if(id==0) numthreads = omp_get_num_threads();
        printf("thread %d \n",id);
        srand(time(0)^omp_get_thread_num());
        for (i=0; i<5; i++)
        {
            printf("thread %d: %d %.6f \n",id,i,(double)rand()/(double)RAND_MAX);
        }
    }
    return 0;
}

您正在使用时间异或(整数,以秒为单位)和线程 ID 来为生成器设定种子。显然不一定在所有线程和所有时间都是唯一的,所以不是一个好主意。使用 std::random_device 的输出调用 srand 会好得多,在大多数机器上(即除微小的嵌入式 CPU 之外的任何东西)都会为您的伪随机生成器播种真实的熵。

来自 https://en.cppreference.com/w/cpp/numeric/random/uniform_real_distribution 的示例代码:

#include <random>
#include <iostream>

int main()
{
    std::random_device rd;  //Will be used to obtain a seed for the random number engine
    std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
    std::uniform_real_distribution<> dis(1.0, 2.0);
    for (int n = 0; n < 10; ++n) {
        // Use dis to transform the random unsigned int generated by gen into a 
        // double in [1, 2). Each call to dis(gen) generates a new random double
        std::cout << dis(gen) << ' ';
    }
    std::cout << '\n';
}

你没有提到你使用的是什么 OS,但是如果它是 Linux 或 POSIX 兼容系统,那么 erand48() for thread-safe generation of random numbers uniformly distributed in the range [0.0, 1.0). It uses a 48-bit seed that's passed as an argument. Generating the initial seed can be done in a number of ways. OpenBSD and Linux have getentropy(), BSDs have arc4random_buf(), you can read from the /dev/urandom special file on many OSes, or do something like you're currently using with time, pid, etc. I'd suggest a higher resolution timer than time(), though - clock_gettime() 是一个很好的选择来源。

一个例子:

#include <errno.h>
#include <fcntl.h>
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void) {
#pragma omp parallel for
  for (int i = 0; i < 4; i++) {
    unsigned short xi[3]; // PRNG state variable

#if 0
    // OpenBSD 5.6+/Linux kernel 3.17+ and glibc 2.25+
    if (getentropy(xi, sizeof xi) < 0) {
      perror("getentropy");
      exit(EXIT_FAILURE);
    }
#else
    // Read from /dev/urandom
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) {
      perror("open /dev/urandom");
      exit(EXIT_FAILURE);
    }
    if (read(fd, xi, sizeof xi) != sizeof xi) {
      perror("read");
      exit(EXIT_FAILURE);
    }
    close(fd);
#endif

    for (int n = 0; n < 4; n++) {
      printf("Thread %d random number %f\n", omp_get_thread_num(), erand48(xi));
    }
  }

  return 0;
}

理想情况下,您应该使用专为并行使用而设计的随机数生成器,这样您可以保证每个线程都对随机数序列的不同部分进行采样。 (向大多数生成器提供任意但不同的种子并不能保证这一点,因为它们不能保证种子的选择如何影响你在序列中的位置。因此你 可能 只是被抵消一个。)

我建议您阅读 Parallel Random Numbers as Easy as 1,2,3 论文,然后使用他们的一种算法,例如在 Intel MKL 中实现的算法(每个人都可以免费获得)。

(MKL supports :-

  • Philox4x32-10 基于计数器的伪随机数生成器 2**128 PHILOX4X32X10[Salmon11].
  • ARS-5 基于计数器的伪随机数生成器,周期为 2**128,使用 AES-NI 集的指令 ARS5 [鲑鱼 11]。 )

通过使用它,您可以轻松确保每个线程的生成器将生成一个独立的序列,这样您就不会在多个线程中对同一个序列进行采样。

英特尔 MKL 可以从 https://software.intel.com/en-us/mkl/choose-download

下载