非常快速的均匀分布随机数发生器

作为蒙特卡罗模拟的一部分,我必须滚动一组骰子,直到某些值出现一定次数。 我执行此操作的代码调用一个骰子类,它生成1到6之间的随机数,并返回它。 最初的代码看起来像

public void roll() { value = (int)(Math.random()*6) + 1; } 

它不是很快。 通过交换Math.random()来实现

 ThreadLocalRandom.current().nextInt(1, 7); 

它在原始时间的大约60%中运行了一个部分,大约有2.5亿次。 作为完整模拟的一部分,它至少会在数十亿次上调用这种方法,那么有没有更快的方法呢?

选择一个随机发生器,该发生器尽可能快速和良好,并且不会通过线程安全机制减慢到正常速度的一小部分。 然后选择一种生成[1..6]整数分布的方法,该分布快速且精确,如您所愿。

最快的简单发电机质量足以击败PRUG的标准测试,例如TestU01 (而不是系统地失败,如Mersenne Twister)是Sebastiano Vigna的 xorshift64 * 。 我将它显示为C代码,但Sebastiano也在Java中使用它:

 uint64_t xorshift64s (int64_t &x) { x ^= x >> 12; x ^= x << 25; x ^= x >> 27; return x * 2685821657736338717ull; } 

Sebastiano Vigna的网站提供了大量有用的信息,链接和基准测试结果。 包括论文,为数学倾向。

在高分辨率下,您可以简单地使用1 + xorshift64s(state) % 6 ,并且偏差将是无法估量的。 如果这还不够快,则通过乘以逆来实现模除法。 如果这还不够快 – 如果你不能为每个变量买两个MUL – 那么它会变得棘手,你需要回到这里。 xorshift1024 * (Java)加上variate的一些技巧将是一个选项。

批处理 – 生成一个充满数字的数组并处理,然后重新填充数组等 – 可以解锁一些速度储备。 在课堂上不必要地包装东西会达到相反的目的。

PS:如果ThreadLocalRandom和xorshift *对于你的目的来说还不够快,即使是批处理,那么你可能会以错误的方式处理事情,或者你可能用错误的语言进行操作。 或两者。

PPS:在Java(或C#,或Delphi)等语言中,抽象不是免费的,它有成本。 在Java中,您还必须考虑强制性无偿数组边界检查等内容,除非您有一个可以消除这些检查的编译器。 从Java程序中汲取高性能可以非常复杂……在C ++中,您可以免费获得抽象和性能。

达斯是正确的,Xorshift *可能是最好的发电机使用。 使用它来填充字节的环形缓冲区,然后一次一个地获取字节以掷骰子,当你获取足够数量时重新填充缓冲区。 为了获得实际的模具滚动,通过使用拒绝采样来避免划分和偏差。 其余的代码看起来像这样(在C中):

 do { if (bp >= buffer + sizeof buffer) { // refill buffer with Xorshifts } v = *bp++ & 7; } while (v > 5); return v; 

这将允许您每64位随机值平均获得6个掷骰子。