java中的快速实值随机生成器

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

我做了一些谷歌搜索,我发现只有基于整数的快速随机生成器。 这里有什么来自区间<0,1的真实数字吗?

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

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

  • Xoroshiro128 + (更快更好)
  • XorShift1024 * (速度相近,甚至更长)

有关这些算法及其质量的信息可以在这个大的PRNG比较中找到 。

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

TLDR

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

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

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

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

至于性能,性能最佳的随机数发生器是线性同余发生器。 其中,我建议使用Numerical Recipes生成器。 您可以从维基百科中查看有关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。

请注意,我提到的所有生成器都不是加密安全的。 如果需要使用加密应用程序的值,则应使用加密安全算法。 但是,我并不认为双打将用于加密。

请注意,所有这些解决方案都错过了一个基本事实(我几周前才知道):使用乘法从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/

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

Imho你应该接受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, WW 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=cd; if(c<0.0) c=c+r; x=xc; 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"); } } 

您是否实际进行了基准测试或分析以certificatenextDouble是瓶颈? 您是否看过MersenneTwister或L’Ecuyer的流库等替代品? 我建议尝试那些有基准测试的人,但没有免费的午餐。 速度的提高可能会以质量为代价,反之亦然。