【问题标题】:How to generate uniformly distributed random numbers between 0 and 1 in a C code using OpenMP?如何使用 OpenMP 在 C 代码中生成 0 和 1 之间的均匀分布的随机数?
【发布时间】:2020-02-19 02:55:44
【问题描述】:

我正在尝试编写一个 OpenMP 代码,其中每个线程将处理 0 到 1 之间均匀分布的随机数的大数组。每个线程需要具有不同且独立的随​​机数分布。此外,每次调用代码时,随机数分布都需要不同。这就是我现在正在使用的。这是否总是保证每个线程都有自己/不同的随机数序列?每次调用代码时序列会有所不同吗?这样做的正确方法是什么?以下代码让每个线程生成 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;
}

【问题讨论】:

  • "并且每次调用代码时随机数分布都需要不同。" --> 最终分布会重复。这个要求有多真实?
  • 谢谢。任何能提供与 Matlab 的 rand 函数类似的性能的东西都可以工作。这就是我使用的参考。
  • time(0)^omp_get_thread_num() 只提供UINT_MAX+1 不同的分布。如果在接下来的几天内多次调用代码,则可能会少得多 - 可能会重复。
  • 代码肯定会连续运行很多天
  • 我关心的不是代码运行多长时间。这是程序被调用的频率。如果只有一次,那么您将受制于rand()RAND_MAX(可能只有 32767)和rand() 状态大小的质量。前面是薄冰,考虑alternatives

标签: c random openmp random-seed


【解决方案1】:

您没有提及您使用的是什么操作系统,但如果它是 Linux 或符合 POSIX 的系统,则有 erand48() 用于线程安全地生成随机数,该随机数均匀分布在 [0.0, 1.0) 范围内。它使用作为参数传递的 48 位种子。可以通过多种方式生成初始种子。 OpenBSD 和 Linux 有getentropy(),BSD 有arc4random_buf(),您可以从许多操作系统上的/dev/urandom 特殊文件中读取,或者执行您当前正在使用的时间、pid 等操作。我建议使用更高的分辨率计时器比 time(),不过 - 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;
}

【讨论】:

  • 你能解释一下如何你理解明确的方向吗,说 "getentropy() 不适用于常规代码;请使用而是使用 arc4random(3) 系列函数。” - 是否有任何内核级副作用导致此显式警告,如引用的手册页中所述?感谢您对此的一束光。
  • @user3666197 就像手册页说的那样,它是用来播种 PRNG 的。您不应该直接使用它来获取大量随机数(OpenBSD 确实为此推送了他们的 arc4random API,但它在采用它的 BSD 之外不可移植,除非您使用 libbsd
【解决方案2】:

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

我建议您阅读Parallel Random Numbers as Easy as 1,2,3 论文,然后使用他们的一种算法,这些算法在例如英特尔 MKL(所有人都可以免费使用)中实现。

(MKL supports:-

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

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

Intel MKL 可以从https://software.intel.com/en-us/mkl/choose-download下载

【讨论】:

    【解决方案3】:

    您正在使用 xor 时间(整数,以秒为单位)和线程 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';
    }
    

    【讨论】:

    • 只是为了指出显而易见的,当第二个滴答作响时,如果线程 1 的时间 = 100 和线程 0 的时间 = 101,两个相邻线程(比如 0 和 1)很容易获得相同的种子.
    • 还有一个被遗忘的部分——问题是关于 C 的,但答案是 C++。如果您在使用 OpenMP 时无法使用 C++,那也没关系。
    • 是的——如果需要纯 C,@Shawn 的答案是最好的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 2015-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多