【问题标题】:Very fast uniform distribution random number generator非常快速的均匀分布随机数发生器
【发布时间】:2015-01-01 03:28:15
【问题描述】:

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

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

而且速度不是很快。通过将 Math.random() 交换为

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

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

【问题讨论】:

  • 你的结果给了你一个提示:并行化应该扩展到你的整个算法,而不仅仅是掷骰子。
  • 我不确定我是否可以分块运行算法,因为它运行的次数取决于滚动值,它可能会在调用随机函数 6 次后终止
  • 算法是什么?正如 duffymo 所指出的,如果您在顶层划分工作,那么您可以让多个线程并行处理数字。在没有看到实际算法/问题的情况下,我们只能提供一般性建议和疯狂猜测。
  • 我明白了,算法是取一组 n 大小的骰子,滚动它们直到条件发生,当它发生时计算它花了多少次,并重复此操作最多 1,000,000 次,我猜这可能是线程的。结果被绘制成图表,所以它经历的迭代越多,结果就越准确,这样图表就越好。该图的重点是大致了解 O 复杂度有多大。

标签: java performance random


【解决方案1】:

选择一个随机生成器,该生成器的速度与您需要的一样好,并且不会因线程安全机制而减慢到其正常速度的一小部分。然后选择一种生成 [1..6] 整数分布的方法,该方法既快速又精确。

最快的简单生成器是Sebastiano Vigna'sxorshift64*,它具有足够高的质量以通过TestU01 等 PRNG 的标准测试(而不是像 Mersenne Twister 那样系统性地失败)。我将其显示为 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) 加上变量的一些技巧将是一种选择。

批处理 - 生成一个充满数字的数组并对其进行处理,然后重新填充数组等等 - 可以解锁一些速度储备。不必要地将东西包装在类中会达到相反的效果。

P.S.:如果 ThreadLocalRandom 和 xorshift* 即使使用批处理也不足以满足您的目的,那么您可能会以错误的方式处理事情,或者您可能会使用错误的语言进行操作。或两者兼而有之。

P.P.S.:在 Java(或 C#,或 Delphi)等语言中,抽象不是免费的,它是有代价的。在 Java 中,您还必须考虑诸如强制性无偿数组边界检查之类的事情,除非您有一个可以消除这些检查的编译器。从 Java 程序中调出高性能可能会非常复杂……在 C++ 中,您可以免费获得抽象和性能。

【讨论】:

  • 还有进一步的优化:首先,使用 64 位 Xorshift 用随机字节填充环形缓冲区,然后在实际掷骰子时一次抓取一个字节(实际上,您只需要三个随机位,但抓取字节消除了很多移位和屏蔽)。然后,使用拒绝采样将您的 3 位值压缩到 1..6。这将拒绝 25% 的字节,但它消除了除法,并且对于 Xorshift 的每个 64 位传递仍然获得 6 次滚动。在 C 语言中,我可以在一分钟内模拟 十亿 手的二十一点。
  • 对,巧妙地解决了除法问题。但是,如果我们在这里谈论 C/asm 级别的性能,您可以用 25% 的机会进行错误预测的跳转,这大约是 MUL 的五倍......我希望有人真的需要像 10^9 die 这样的东西每秒滚动,这样我们就有了突破极限的借口。 :-) 无论如何,您的解决方案非常非常难以击败。我喜欢它。
  • 该死的事情不允许我编辑我的评论......我想修改我对 Lee 的解决方案的评估,以解决“fanta-fricken-tastic 并且很可能无法击败”。信用到期。
  • 作者要求统一随机数。 @DarthGizka,你能说一下用 xorshift 生成这样的数字吗?
  • @kelin:作者询问了生成统一整数数的性能,这是我们一直在谈论的。如果您的问题是关于生成统一的 浮点 数,那么这是另一回事,并且评论太短而无法进行合理的处理(除了说需要小心,因为幼稚的方法往往会产生有偏差的结果)。你真的应该把它作为一个单独的问题来问——前提是它还没有被问到。
【解决方案2】:

Darth 是正确的,Xorshift* 可能是最好的生成器。使用它来填充字节的环形缓冲区,然后一次获取一个字节来掷骰子,当你获取足够多的时候重新填充缓冲区。要获得实际的骰子,请使用拒绝抽样来避免分裂和偏差。其余的代码看起来像这样(在 C 中):

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-16
    • 2011-03-02
    • 2013-07-22
    • 1970-01-01
    • 2011-08-08
    • 2014-02-04
    • 1970-01-01
    相关资源
    最近更新 更多