web-dev-qa-db-ja.com

java)の高速実数値乱数ジェネレータ

Java.util.Random.nextDouble()は私にとって遅いので、本当に速いものが必要です。

私はいくつかのグーグル検索をしました、そして私は整数ベースの高速ランダムジェネレーターだけを見つけました。区間<0、1)の実数について何かありますか?

10
Snurka Bill

高速なものが必要でJava8にアクセスできる場合は、Java.utilsSplitableRandom をお勧めします。それはより速く(〜2倍の速さ)、より良い統計分布を持っています。

さらに高速またはより優れたアルゴリズムが必要な場合は、次の特殊なXorShiftバリアントのいずれかをお勧めします。

これらのアルゴリズムとその品質に関する情報は、 this big PRNG compare

私は独立したパフォーマンス比較を行いました。詳細な結果とコードはここにあります: github.com/tobijdc/PRNG-Performance

[〜#〜] tldr [〜#〜]

Java.util.Randomは使用せず、Java.util.SplittableRandomを使用してください。より速くまたはより良いものが必要な場合PRNG XorShiftバリアントを使用してください。

26
tobijdc

次の方法で、整数ベースのRNGを変更して、[0,1)の間隔でdoubleを出力することができます。

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

ただし、randInt()が32ビット整数を生成する場合、doubleには53の仮数ビットがあるため、doubleのすべてのビットが満たされるわけではありません。明らかに、2つのランダムな整数を生成して、すべての仮数ビットを埋めることができます。または、Ramdom.nextDouble()実装のソースコードを確認することもできます。ほぼ確実に整数RNGを使用し、出力をdoubleに変換するだけです。

パフォーマンスに関しては、最もパフォーマンスの高い乱数ジェネレーターは線形合同ジェネレーターです。これらのうち、NumericalRecipesジェネレーターの使用をお勧めします。ウィキペディアからLCGの詳細を確認できます: http://en.wikipedia.org/wiki/Linear_congruential_generator

ただし、優れたランダム性が必要で、パフォーマンスがそれほど重要でない場合は、メルセンヌツイスターが最良の選択だと思います。ウィキペディアのページもあります: http://en.wikipedia.org/wiki/Mersenne_Twister

PCGと呼ばれる最近の乱数ジェネレーターがあります。これは http://www.pcg-random.org/ で説明されています。これは本質的に、LCG出力のランダム性を向上させるLCGの後処理ステップです。 PCGはLCGの後処理ステップにすぎないため、LCGよりも低速であることに注意してください。したがって、パフォーマンスが非常に重要で、ランダム性の品質がそれほど重要でない場合は、PCGの代わりにLCGを使用する必要があります。

私が言及したジェネレーターはどれも暗号的に安全ではないことに注意してください。暗号化アプリケーションの値を使用する必要がある場合は、暗号化的に安全なアルゴリズムを使用する必要があります。ただし、暗号化にdoubleが使用されるとは思いません。

3
juhist

これらのソリューションはすべて、基本的な事実(数週間前までは気づいていなかった)を見逃していることに注意してください。乗算を使用して64ビットからdoubleに渡すと、時間の大きな損失になります。 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/

3
seba

私見あなたはただjuhistの答えを受け入れる-これが理由です。

nextDoubleはnext()を2回呼び出すため、低速です。ドキュメントに記載されています。

したがって、最良のオプションは次のとおりです。

  • 高速64ビットジェネレーターを使用し、それをdoubleに変換します(MT、PCG、xorshift *、ISAAC64、...)
  • ダブルスを直接生成する

これは、JavaのRandom、LCG(Java.util.Randomと同じくらい悪い)、およびMarsagliaのユニバーサルジェネレーター(doubleを生成するバージョン)を使用した非常に長いベンチマークです。

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");
    }
}
1
loreb

プログラムを初期化してそれを繰り返すときに、ランダムなdoubleの配列を作成できます。これははるかに高速ですが、ランダムな値が再び現れます。

1
Alexanus