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

时间:2014-11-05 02:23:58

标签: java performance random

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

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

并且它不是很快。通过交换Math.random()来获取

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

它在原始时间的大约60%中运行了一段,大约有2.5亿次。 作为完整模拟的一部分,它至少会使用这种方法数十亿次,所以有更快的方法吗?

2 个答案:

答案 0 :(得分:17)

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

最快的简单生成器具有足够高的质量,可以胜过PRNG的标准测试,例如TestU01(而不是系统地失败,如Mersenne Twister)Sebastiano Vigna's xorshift64*。我将其显示为C代码,但Sebastiano将其显示为in Java as well

uint64_t xorshift64s (int64_t &x)
{
   x ^= x >> 12;
   x ^= x << 25;
   x ^= x >> 27;

   return x * 2685821657736338717ull;
}

Sebastiano Vigna's site有很多有用的信息,链接和基准测试结果。包括论文,用于数学倾向。

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

批处理 - 生成一个充满数字的数组并处理,然后重新填充数组等等 - 可以解锁一些速度储备。在课堂上不必要地将事情包装成相反的事情。

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

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

答案 1 :(得分:1)

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

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

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