【问题标题】:repeat random generation in parallel in c++, avoiding duplication over multiple runs在 C++ 中并行重复随机生成,避免多次运行重复
【发布时间】:2018-12-01 21:56:22
【问题描述】:

这可能是一个非常简单的问题,但我对 C++ 上的随机数生成非常陌生,并希望确保我得到它是正确的。

我有一个随机函数,我想多次并行运行,因此函数的每次并行运行都需要与其他运行不同,并且与以前的运行不同,据我了解,我可以这样做的一种方法是将 random_device 作为每个的种子。例如

for (int i= 0; i< runs; i++){
//do something
#pragma omp parallel for schedule(static)
for (int j = 0; j < std::size(iters); j++){
    std::mt19937 mrandThread(std::random_device{}());
    iters.at(j) = stochFunction(parameters, mrandThread);
}
//do something
}

但是,这似乎在计算上会很昂贵,因为您会多次启动 random_device,尤其是在上述循环重复很多次的情况下。另一个问题是运行可能会重复,因为 random_device 只是设置一个种子,它可能会再次出现?但是,目前从循环外部传递引擎,例如

std::mt19937 mrandThread(std::random_device{}());
for (int i= 0; i< runs; i++){
//do something
#pragma omp parallel for schedule(static)
for (int j = 0; j < std::size(iters); j++){
    iters.at(j) = stochFunction(parameters, mrandThread);
}
//do something
}

意味着每个线程都给出相同的结果,因为它们只是从并行发送的那一点开始运行梅森捻线机的副本?我见过的另一个选择是使用 rand_r(),但这是否可能再次出现类似于种子重复的问题,或者这更像是当前随机轨迹集在循环外的分支?

任何关于如何最好地实现这一点的建议将不胜感激。

【问题讨论】:

  • 您可以简单地为每个线程分配它自己的随机生成器实例(使用不同的种子)。这样,您只需分配种子 次,而不是每次迭代。

标签: c++ random parallel-processing mersenne-twister


【解决方案1】:

我认为类似于#pragma omp parallel private(mrandThread) 的东西可以解决问题,这意味着:

private 指令声明数据在 每个线程的内存。这样的私有变量被初始化为它们 将在主程序中。任何计算值在结束时消失 平行区域。 (但是,请参见下文。)因此,您不应依赖 在任何初始值上,或在外部变量的值之后 地区。

(来自:http://pages.tacc.utexas.edu/~eijkhout/pcse/html/omp-data.html

【讨论】:

  • 应该小心,默认种子是一个固定值(如果我正确阅读了文档),你不希望有几个相同的生成器 - 这会破坏并行运行它的目的.
  • 我的理解是std::random_device 应该处理这个问题,但也许不是在所有平台上。见en.cppreference.com/w/cpp/numeric/random/random_device。绝对值得仔细检查
  • 我不知道,我的理解是,在循环外初始化一次,然后为每个线程创建一个副本——不是你想要的。但我可能错了,检查起来应该很简单。
  • 感谢您的帮助,我正在阅读文档并查看它的工作原理等。在循环外初始化似乎复制了生成器,导致相同的运行,但我可能错过了一些东西。
  • 正如应该的那样,只需创建一个并行区域,其中每个线程都有一个私有生成器,然后迭代案例,或者你可以非常懒惰并创建一个生成器的数组/向量,这里有一些开销但您是否应该关心这取决于您的stochFunction 的复杂性。
【解决方案2】:

如果您想确保每个线程都有唯一的种子,我可能会提前生成种子,然后在启动线程时使用它们:

std::vector<int> gen_seeds(int num) {
    // start by stuffing them into a set to guarantee uniqueness.
    std::set<int> s;

    std::random_device g;

    while (s.size() < num)
        s.insert(g());

    // then return them in a vector to give random access:
    std::vector<int> seeds(s.begin(), s.end());
    return seeds;
}

// generate N unique seeds:
auto seeds = gen_seeds(std::size(iters));

#pragma omp parallel for schedule(static)
for (int j = 0; j < std::size(iters); j++){
    // and use each to seed a generator:
    std::mt19937 mrandThread(seeds[j]);
    iters.at(j) = stochFunction(parameters, mrandThread);
}

理论上,在每个线程中调用random_device 可能会快一点,但实际上不太可能——无论如何,它通常是具有序列化访问权限的硬件设备。通过在单个线程中提前完成(并使用一组或类似的),确保每个种子真正唯一是微不足道的,这可能比在生成种子时节省一微秒更重要。

哦:还有一点:使用来自 random_device 的单个结果来播种 Mersenne twister 也不是完全最优的。生成器类可以告诉您它们的种子数据的大小 - 理想情况下,您希望用这么多数据为它们播种。

【讨论】:

  • 你如何确定序列不会重叠?
  • @SeverinPappadeux:至少就目前而言,你不是——但问题没有提到任何阻止这种情况发生的愿望。如果您真的想阻止它发生,您可能希望首先将所有输入生成为单个流,然后生成线程,将每个线程传递给该单个流的自己的部分。
  • but the question doesn't mention any desire to prevent that from happening 真的吗?!?问题是so each parallel run of the function needs to be different from the other and different from previous runs,如果序列重叠,会有什么不同?
猜你喜欢
  • 2017-12-26
  • 2014-02-05
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 2019-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多