3.6 并行 RNG
3 分钟阅读
原文链接: https://rust-random.github.io/book/guide-parallel.html
理论:多个 RNG
若要在多个工作线程中同时使用随机生成器,则需要使用多个 RNG。几种建议做法:
- 在每个工作线程中使用
rng()。它会在每个使用它的线程上自动(惰性且唯一地)播种。 - 用
rng()(或另一个主 RNG)为每个工作线程上的自定义 RNG 播种。主要优势是对所用 RNG 更灵活。 - 每个工作单元使用一个自定义 RNG,而非每个工作线程。若这些 RNG 以确定性方式播种,则可得到确定性结果。遗憾的是,从主生成器为每个工作单元播种新 RNG 无法并行完成,因此可能较慢。
- 使用单一主种子。对每个工作单元,用主种子播种 RNG 并将 RNG 的流设置为工作单元编号。这可能比 (3) 更快且仍保持确定性,但并非所有 RNG 都支持。
注意:不要简单地为工作线程/单元克隆 RNG。克隆会产生与原始 RNG 相同的输出序列。但若随后为每个克隆设置唯一流,则可以使用克隆。
流
哪些 RNG 族支持多流?
- ChaCha:ChaCha RNG 支持 256 位种子、64 位流和 64 位计数器(每 16 字块),因此支持 264 条流,每条 268 个字。
- Hc128 是支持 256 位种子的密码学 RNG;可从(例如)较小的 192 位密钥加 64 位流构造该种子。
注意,从较小密钥加流计数器构造种子的上述方法仅建议用于密码学 PRNG,因为较简单的 RNG 在使用两个相近密钥时输出往往相关,且可能需要「看起来随机」的种子才能产生高质量输出。
非密码学 PRNG 仍可能支持多流,但很可能有显著限制(尤其要注意此类 PRNG 的常见建议:消耗量不要超过生成器周期的平方根)。
Xoshiro:Xoshiro 族 RNG 支持
jump和long_jump方法,可有效将单个 RNG 的输出划分为多条流。实践中仅对少量流有用,因为要选择第 n 条「流」必须调用jumpn次。Pcg:这些 RNG 支持用
state和stream参数构造。但请注意,使用相同密钥的多条流往往高度相关,RNG 因此受到批评。见作者自己的评论。PCG RNG 还支持
fn advance(delta)方法,可用于像 Xoshiro 的jump那样将单一流划分为多条子流(但更好,因为可指定偏移)。
实践:非确定性多线程
我们使用 Rayon 的并行迭代器,用 map_init 在每个工作线程中初始化 RNG。注意:该 RNG 可能在多个工作单元间复用,工作单元可能以非确定性方式在工作线程间拆分。
| |
实践:确定性多线程
我们使用上述方法 (4) 实现确定性结果:从单一种子初始化所有 RNG,但使用多条流。
我们使用 ChaCha8Rng::set_stream 实现这一点。
此外,我们按 BATCH_SIZE 手动批处理多个工作单元。这很重要,因为初始化 RNG 的成本相对于我们的工作单元(生成两个随机样本加一些简单计算)很大。手动批处理也能提升上述非确定性模拟的性能。
(注意:此示例见 https://github.com/rust-random/rand/blob/master/examples/rayon-monte-carlo.rs。)
| |