【问题标题】:Fast real valued random generator in javajava中的快速实值随机生成器
【发布时间】:2015-05-25 10:43:20
【问题描述】:

java.util.Random.nextDouble() 对我来说很慢,我需要一些非常快的东西。

我做了一些谷歌搜索,我发现只有基于整数的快速随机生成器。对于区间

【问题讨论】:

  • 有多慢?您需要多快?
  • 还有,随机性和安全性如何。如果您在 linux 中使用 SecureRandom,您可能需要等待更多的系统熵,请参阅tldp.org/HOWTO/Security-HOWTO/kernel-security.html#AEN806
  • 我用它来模拟。它不需要是安全的。我将它用于随机模型,所以我需要很多随机数。我正在计算一切的概率,我发现这是我的瓶颈
  • 我进行了基准测试。每个数字代表每秒生成的随机双精度数:38734085 39133701 39352714 39353135 39353381。我的 CPU 是Intel Core i5-2400 @ 4x 3.4GHz。这对你来说还不够快吗?

标签: java performance random double generator


【解决方案1】:

如果您需要快速的东西并且可以访问 Java8,我可以推荐 java.utils SplittableRandom。它更快(〜两倍快)并且具有更好的统计分布。

如果您需要更快或更好的算法,我可以推荐以下专门的 XorShift 变体之一:

有关这些算法及其质量的信息可以在this big PRNG comparison 中找到。

我做了一个独立的性能比较你可以在这里找到详细的结果和代码:github.com/tobijdc/PRNG-Performance

TLDR

永远不要使用java.util.Random,使用java.util.SplittableRandom。 如果您需要更快或更好的 PRNG,请使用 XorShift 变体。

【讨论】:

  • 在我的测试中,SplitableRandom 比旧的 Random 类快 30 倍。它在大数据测试中非常好。谢谢。
  • @Msfvtp 你是如何进行测试的?
  • 我使用具有 5 亿次迭代的循环对其进行测试。 12 秒只用了 0.4 秒。我在循环中调用了 nextDouble 方法。
  • 错字:SplittableRandom,不是SplitableRandom
  • 在 Java 中,现有的 XorShift 实现似乎比 SplittableRandom / ThreadLocalRandom 慢。 dsiutils.di.unimi.it/docs/it/unimi/dsi/util/…
【解决方案2】:

您可以通过以下方式修改基于整数的 RNG 以在区间 [0,1) 内输出双精度:

double randDouble = randInt()/(RAND_INT_MAX + 1.0)

但是,如果 randInt() 生成一个 32 位整数,则不会填充 double 的所有位,因为 double 有 53 个尾数位。您显然可以生成两个随机整数来填充所有尾数位。或者您可以查看 Ramdom.nextDouble() 实现的源代码。它几乎肯定会使用整数 RNG,并将输出简单地转换为双精度。

就性能而言,性能最好的随机数生成器是线性同余生成器。其中,我推荐使用数字食谱生成器。您可以从 Wikipedia 中查看有关 LCG 的更多信息:http://en.wikipedia.org/wiki/Linear_congruential_generator

但是,如果你想要好的随机性和性能不是那么重要,我认为 Mersenne Twister 是最好的选择。它还有一个维基百科页面:http://en.wikipedia.org/wiki/Mersenne_Twister

最近有一个名为 PCG 的随机数生成器,在 http://www.pcg-random.org/ 中进行了解释。这本质上是 LCG 的后处理步骤,可提高 LCG 输出的随机性。请注意,PCG 比 LCG 慢,因为它只是 LCG 的后处理步骤。因此,如果性能非常重要而随机性质量不那么重要,那么您应该使用 LCG 而不是 PCG。

请注意,我提到的所有生成器都不是加密安全的。如果您需要将这些值用于加密应用程序,您应该使用加密安全算法。但是,我真的不相信双打会用于密码学。

【讨论】:

    【解决方案3】:

    请注意,所有这些解决方案都忽略了一个基本事实(直到几周前我还没有意识到):使用乘法从 64 位传递到双精度会浪费大量时间。 DSI 实用程序 (http://dsiutils.di.unimi.it/) 中 xorshift128+ 和 xorshift1024+ 的实现使用直接位操作,结果令人印象深刻。

    查看 nextDouble() 的基准

    http://dsiutils.di.unimi.it/docs/it/unimi/dsi/util/package-summary.html#package.description

    以及报告的质量

    http://prng.di.unimi.it/

    【讨论】:

      【解决方案4】:

      恕我直言,您应该接受 juhist 的回答 - 这就是原因。

      nextDouble 很慢,因为它对 next() 进行了两次调用 - 它就写在文档中。

      所以你最好的选择是:

      • 使用快速 64 位生成器,将其转换为双精度(MT、PCG、xorshift*、ISAAC64、...)
      • 直接生成双打

      这是一个过长的基准测试,其中包含 java 的 Random、LCG(与 java.util.Random 一样糟糕)和 Marsaglia 的通用生成器(生成双精度的版本)。

      import java.util.*;
      
      public class d01 {
          private static long sec(double x)
          {
              return (long) (x * (1000L*1000*1000));
          }
          // ns/op: nanoseconds to generate a double
          // loop until it takes a second.
          public static double ns_op(Random r)
          {
              long nanos = -1;
              int n;
              for(n = 1; n < 0x12345678; n *= 2) {
                  long t0 = System.nanoTime();
                  for(int i = 0; i < n; i++)
                      r.nextDouble();
                  nanos = System.nanoTime() - t0;
                  if(nanos >= sec(1))
                      break;
                  if(nanos < sec(0.1))
                      n *= 4;
              }
              return nanos / (double)n;
          }
          public static void bench(Random r)
          {
              System.out.println(ns_op(r) + " " + r.toString());
          }
      
          public static void main(String[] args)
          {
              for(int i = 0; i < 3; i++) {
                  bench(new Random());
                  bench(new LCG64(new Random().nextLong()));
                  bench(new UNI_double(new Random().nextLong()));
              }
          }
      }
      
      // straight from wikipedia
      class LCG64 extends java.util.Random {
          private long x;
          public LCG64(long seed) {
              this.x = seed;
          }
          @Override
          public long nextLong() {
              x = x * 6364136223846793005L + 1442695040888963407L;
              return x;
          }
          @Override
          public double nextDouble(){
              return (nextLong() >>> 11) * (1.0/9007199254740992.0);
          }
          @Override
          protected int next(int nbits)
          {
              throw new RuntimeException("TODO");
          }
      }
      
      
      class UNI_double extends java.util.Random {
          // Marsaglia's UNIversal random generator extended to double precision
          // G. Marsaglia, W.W. Tsang / Statistics & Probability Letters 66 (2004) 183 – 187
          private final double[] U = new double[98];
          static final double r=9007199254740881.0/9007199254740992.;
          static final double d=362436069876.0/9007199254740992.0;
          private double c=0.;
          private int i=97,j=33;
          @Override
          public double nextDouble(){
                  double x;
      
                  x=U[i]- U[j];
                  if(x<0.0)
                      x=x+1.0;
                  U[i]=x;
      
                  if(--i==0) i=97;
                  if(--j==0) j=97;
      
                  c=c-d;
                  if(c<0.0)
                      c=c+r;
      
                  x=x-c;
                  if(x<0.)
                      return x+1.;
                  return x;
              }
          //A two-seed function for filling the static array U[98] one bit at a time
          private
              void fillU(int seed1, int seed2){
                  double s,t;
                  int x,y,i,j;
                  x=seed1;
                  y=seed2;
      
                  for (i=1; i<98; i++){
                      s= 0.0;
                      t=0.5;
      
                      for (j=1; j<54; j++){
                          x=(6969*x) % 65543;
                          // typo in the paper:
                          //y=(8888*x) % 65579;
                          //used forthe demo in the last page of the paper.
                          y=(8888*y) % 65579;
                          if(((x^y)& 32)>0)
                              s=s+t;
                          t=.5*t;
                      }
                      if(x == 0)
                          throw new IllegalArgumentException("x");
                      if(y == 0)
                          throw new IllegalArgumentException("y");
                      U[i]=s;
                  }
              }
      
          // Marsaglia's test code is useless because of a typo in fillU():
          //  x=(6969*x)%65543;
          //  y=(8888*x)% 65579;
      
          public UNI_double(long seed)
          {
              Random r = new Random(seed);
              for(;;) {
                  try {
                      fillU(r.nextInt(), r.nextInt());
                      break;
                  } catch(Exception e) {
                      // loop again
                  }
              }
          }
      
          @Override
          protected int next(int nbits)
          {
              throw new RuntimeException("TODO");
          }
      }
      

      【讨论】:

        【解决方案5】:

        您可以在初始化程序时创建一个随机双精度数组,然后重复它。这要快得多,但随机值会重复它们自己。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-05
          • 1970-01-01
          • 2020-05-23
          • 1970-01-01
          • 2016-02-21
          • 1970-01-01
          • 2013-03-18
          • 2012-03-13
          相关资源
          最近更新 更多