多个线程的随机数

时间:2013-02-17 17:31:27

标签: c++ multithreading boost random mersenne-twister

问题

我打算为Linux编写一个C ++ 11应用程序,它基于大约一百万个伪随机32位数进行一些数值模拟(不是加密)。为了加快速度,我想使用桌面CPU的所有内核在并行线程中执行模拟。我想使用由boost提供的Mersenne Twister mt19937作为PRNG,我想由于性能原因,每个线程我应该有一个这样的PRNG。现在我不确定如何播种它们以避免在多个线程中生成相同的随机数子序列。

替代

以下是我到目前为止所考虑的替代方案:

  1. 独立于/dev/urandom为每个线程播种PRNG。

    当系统熵池耗尽时,我有点担心,因为我不知道系统内部PRNG是如何运行的。由于/dev/urandom正在使用Mersenne Twister本身这一事实,我是否意外地获得了连续种子,这些种子准确地识别了Mersenne Twister的连续状态?可能与我对下一点的担忧密切相关。

  2. /dev/urandom种植一个PRNG,从第一个PRNG播种。

    基本上也是同样的问题:使用一个PRNG来播种另一个使用相同算法的PRNG是好还是坏?或者换句话说,在这一代中,从mt19937读取625个32位整数是否直接对应于mt19937生成器的内部状态?

  3. 首先使用非梅森信息为其他人提供种子。

    使用相同的算法生成随机数并生成初始种子感觉某种方式可能是一个坏主意,我考虑引入一些不依赖于Mersenne Twister算法的元素。例如,我可以将线程id与初始种子向量的每个元素进行异或。这会让事情变得更好吗?

  4. 在线程中共享一个PRNG。

    这将确保只有一个序列,具有Mersenne Twister的所有已知和理想的属性。但是控制对该生成器的访问所需的锁定开销确实让我有点担心。由于我没有发现任何相反的证据,我认为我作为图书馆用户将负责防止并发访问PRNG。

  5. 预先生成所有随机数。

    这将有一个线程预先生成所有必需的1M随机数,稍后将由不同的线程使用。与整个应用程序相比,4M的内存要求会很小。在这种方法中最让我担心的是随机数本身的产生并不是并发的。整个方法也不能很好地扩展。

  6. 问题

    您会建议以下哪种方法?为什么?或者你有不同的建议吗?

    您是否知道我的哪些问题是合理的,哪些是因为我对事情的实际运作缺乏洞察力?

8 个答案:

答案 0 :(得分:7)

我会选择#1,从urandom中播种每个prng。这确保状态完全独立(就种子数据而言是独立的)。除非你有很多线程,否则通常会有足够的熵可用。此外,根据用于/ dev / urandom的算法,您几乎肯定不需要担心它。

因此,您可以使用以下内容创建每个prng:

#include <random>

std::mt19937 get_prng() {
    std::random_device r;
    std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()};
    return std::mt19937(seed);
}

您应该验证std::random_device的实施是否来自您配置下的/dev/urandom。如果它默认使用/ dev / urandom,那么如果你想使用/ dev / random,通常可以说std::random_device("/dev/random")

答案 1 :(得分:5)

您可以使用具有不同代数结构的PRNG来播种不同的PRNG。例如。一些MD5哈希序列。

但是我会选择#5。如果它工作,那就没关系。如果没有,您仍然可以优化它。

重点是创建 PRNG比人们预期的要难得多。线程应用程序的良好PRNG很可能仍然需要进行研究。

如果CPU的数量足够低,你就可以逃脱掠夺。例如。如果你有4个核心,用相同的值初始化所有核心,但是将核心1 PRNG提前1,#2提高,#3提高3.然后当你需要一个新数字时,总是提前4步。

答案 2 :(得分:3)

我会使用一个实例来播种其他实例。我很确定你可以很容易地安全地做到这一点。

  • 即使状态空间的微小变化也会导致下游相当大的变化 - 如果你能确保它们没有完全相同的起始空间(并且没有相同的状态前缀),我不会担心产生相同的数字。例如,仅使用值1,2,3来生成三个线程就可以正常工作 - 您甚至不需要为整个空间播种。另一个优点是:通过使用清晰可预测的种子,您可以轻易地诋毁您正在挑选任何运行的想法(假设您正在尝试演示某些内容)。
  • 以某种方式播种是微不足道的,这意味着由此产生的“孩子”高度不相关。只是以广度优先的方式进行迭代;即如果你想种子N x 623 int值,不要顺序播种623值,但是选择前N个并分配,然后是下一个N等。即使播种者和孩子之间存在某种相关性,不同的孩子应该几乎不存在 - 而这就是你所关心的。
  • 我更喜欢一种允许确定性执行的算法,因此在urandom上依赖并不具吸引力。这使调试更容易。
  • 最后,显然 - 测试。这些PRNG非常强大,但无论如何都要注意结果,并根据您的模拟灵感进行一些相关性测试。大多数问题都应该是显而易见的 - 你要么播种得很糟糕,并且有明显重复的后续序列,你们播种得很好,然后质量是由PRNG的限制决定的。
  • 对于最终执行,在完成测试后,您可以使用urandom对623个状态值中的第一个进行播种,以保证高枕无忧和/或线程ID。

答案 3 :(得分:3)

种子线1带1,种子线2带2等

如果您需要monte carlo,这将为您提供可重现的结果,易于跟踪和实施。

答案 4 :(得分:2)

请查看以下文件:Dynamic Creation of Pseudorandom Number Generators以及随附的实施:Dynamic Creator。它解决了这个确切的问题。

答案 5 :(得分:2)

如果您真的想在数学上正确,请使用SFMT算法作者提供的跳转功能。跳转功能保证两个不同PRNG流之间的最小序列数。

但实际上,/ dev / urandom初始化就足够了。

答案 6 :(得分:1)

我会说#3是胜利者。使用processID或threadID等方法为每个线程设定种子;虽然技术上可能会有重叠,但这种可能性很小。一旦你离开单个数字,即使是连续的数字也不应该与种子相关(我不知道Twister算法,但我见过的最差的PRNG在7以上是好的)。与大多数PRNG方程的范围相比,100万PRNG并不是那么多。

最后,你可以很容易地检查。检查每个线程生成的 last 种子,以防止每个其他线程中的所有数字。如果种子出现在线程中,则检查每个线程中生成的前一个数字;如果它们也匹配,那么你就会发生碰撞并需要重新播种你的溪流并再试一次。

答案 7 :(得分:1)

有一个实施(和发表的论文)专门涉及使用Mersenne Twister进行并行计算。这是由MT的原作者。他们称之为“动态创建者”,可以在这里找到:

http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/DC/dc.html

这将是研究MT19937特定用途的一个非常好的地方,特别是那里的纸张。