web-dev-qa-db-ja.com

Java 7またはJava 6でのランダムUUID生成のパフォーマンス

WebベースのJavaセッション情報用のランダムUUIDを生成するアプリケーションがあります。テスターの1人が、独自のプロファイリングに基づいてUUIDを生成するために最大350msを要求していますが、まだできません。彼の結果を再現します。彼はこの記事を参照しています http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html 彼の結果をバックアップするのに役立ちます。それ以外の場合は、Java 6またはJava 7アプリケーションのいずれかでJavaの組み込みUUID生成機能を使用して、この制限に遭遇しました。

17
Shawn H

私はそれをテストしました

    for (;;) {
        long t0 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            UUID.randomUUID();
        }
        System.out.println(System.currentTimeMillis() - t0);
    }

私のPCでは、それはかなり遅い1100 msです。 UUID.randomUUID()は内部でSecureRandomを使用して、通常のJava.util.Randomをより速く使用できるようにします

    Random r = new Random();
    for (;;) {
            ..
            new UUID(r.nextLong(), r.nextLong());

それは〜80ミリ秒です

10

これはベータ127で実行したテストです。

このテストは非現実的であり、私が想像できる最悪のシナリオを超えていることに注意してください。私の目標は、批判を裏付ける事実なしに [〜#〜] uuid [〜#〜] sの悪口を言う人を黙らせることでした。

シナリオ:

  • 100万回の呼び出しのタイトループ Java.util.UUID.randomUUID()
    • それだけで1つのテスト。 (競合なし)
    • 他の2つのスレッドがタイトなループにあり、10万万回の呼び出しを行う、競合のある1つのテスト。
  • Java 8ベータ127
    • Javaバージョン "1.8.0"
    • Java(TM)SEランタイム環境(ビルド1.8.0-b127)
    • Java HotSpot(TM)64ビットサーバーVM(ビルド25.0-b69、混合モード)
  • Netbeans 7.4 IDEから実行
  • 仮想マシン内で実行
  • Mac mini (2012年後半)

競合なし

1つのスレッドで1つのループを実行するため、同期されたメソッド/クラスの競合はありません。

// Warm the random generator.
Java.util.UUID uuid;
uuid = Java.util.UUID.randomUUID();

long stop = 0;
long start = System.nanoTime();

int loops = 1000000;  // One million.
for ( int i = 0; i < loops; i++ ) {
    uuid = Java.util.UUID.randomUUID();
}

stop = System.nanoTime();

long elapsed = ( stop - start );

System.out.println( "UUIDs: " + loops );
System.out.println( "Nanos: " + elapsed );
System.out.println( "Nanos per uuid: " + ( elapsed / loops ) + " ( micros per: " + ( elapsed / loops / 1000 ) + " )" );

結果

UUIDあたり約2マイクロ秒

競合あり

上記と同様ですが、100万回の呼び出しのループを実行している間、それぞれがten100万回の呼び出しを行う2つのスレッドが実行されています。

// Warm the random generator.
Java.util.UUID uuid;
uuid = Java.util.UUID.randomUUID();

int pass = 10_000_000 ;  // Ten million.
MyThread t1 = new MyThread( pass );
MyThread t2 = new MyThread( pass );


t1.start();
t2.start();
t3.start();

long stop = 0;
long start = System.nanoTime();

int loops = 1_000_000 ;  // One million.
for ( int i = 0; i < loops; i++ ) {
    uuid = Java.util.UUID.randomUUID();
}

stop = System.nanoTime();

long elapsed = ( stop - start );

System.out.println( "UUIDs: " + loops );
System.out.println( "Nanos: " + elapsed );
System.out.println( "Nanos per uuid: " + ( elapsed / loops ) + " ( micros per: " + ( elapsed / loops / 1000 ) + " )" );

そして、各スレッドを定義するクラス…

class MyThread extends Thread {

    private int loops;

    public MyThread( int loops ) {
        this.loops = loops;
    }

    @Override
    public void run() {
        Java.util.UUID uuid;
        for ( int i = 0; i < this.loops; i++ ) {
            uuid = Java.util.UUID.randomUUID();
        }

    }
}

結果

UUIDあたり約20マイクロ秒

実行は、UUIDあたり14、20、20、23、および24マイクロ秒でした(この順序ではありません)。したがって、極端な競合下では約10倍悪くなり、20マイクロ秒は、私が知っている実際の使用状況では受け入れられます。

19
Basil Bourque

UUIDのランダムな形式は、通常、「暗号強度」の乱数のソースを使用します。

(それができなかった場合、いわゆるランダムUUIDが予測可能になり、指定されたUUIDが再発行される可能性が心配なレベルまで増加する可能性があります。別の回答が示唆するように、高速(ただし弱い)PRNGをUUIDコンストラクタに渡します。しかし、それは悪い考えです。)

一般的な暗号強度の乱数ジェネレータは、アプリケーションの外部にあるエントロピーのソースを使用します。それはハードウェア乱数ジェネレーターかもしれませんが、より一般的には、通常の操作でオペレーティングシステムによって収集される「ランダムさ」が蓄積されます。問題は、エントロピーのソースにレート制限があることです。一定の期間にわたってそのレートを超えた場合、ソースを排出できます。次に何が起こるかはシステムに依存しますが、システムによっては、エントロピーを読み取るためのシステムコールが...が利用可能になるまで停止します。

それがクライアントのシステムで起こっていることだと思います。 (仮想マシンでは珍しくありません...)

(Linuxシステムの)ハックな回避策の1つは、rngdデーモンをインストールし、適切な疑似乱数ジェネレータを使用してエントロピープールを「補充」するようにデーモンを構成することです。セキュリティの専門家は次のように指摘します。

  • これはUUIDジェネレーターのランダム性に影響します。
  • エントロピープールは他のセキュリティ関連のものに使用されるので、疑わしいソースから追​​加すると弱体化します。

このハックが実際にどれほど安全かはわかりません。

遅い乱数生成についての別のQ&Aは次のとおりです。

12
Stephen C

スレッドの数は、UUIDの生成のパフォーマンスに大きな影響を与えます。これは、UUID.randomUUID()の乱数を生成するSecureRandom#nextBytes(byte[]の実装を見ると説明できます。

synchronized public void nextBytes(byte[] bytes) {
    secureRandomSpi.engineNextBytes(bytes);
}

nextBytessynchronizedであり、異なるスレッドからアクセスされるとパフォーマンスが大幅に低下します。

9
Peter Keller

4の代わりにバージョン1を使用する

バージョン1タイプのUUIDを使用するのはどうですか?

バージョン1MACアドレス と現在の時刻(「空間と時刻」)に基づいています。バージョン4よりも衝突の可能性ははるかに低くなります。

バージョン4 は、暗号学的に強力なランダムジェネレーターを使用して乱数から完全に生成されることに基づいています。

Oracle JVMはバージョン1ジェネレーターを提供していません。これは明らかにセキュリティとプライバシーの問題のためです。 JVMは、ホストマシンのMACアドレスへのアクセスを提供しません。

JUGライブラリ

バージョン1 UUIDとその他のバージョンを提供するサードパーティライブラリが少なくとも1つあります。 JUG – Java UUID Generator 。 Java 6で導入された機能により、MACアドレスにアクセスできると彼らは言っています。

テスト結果:20x

2010記事のJava UUID Generatorバージョン3を使用したテスト結果によるパフォーマンスの説明を読んでください More on Java UUIDジェネレーター(JUG)、パフォーマンスの言葉 。 Tatu Salorantaは、MacBookでさまざまな種類のUUIDをテストしました。

アップショット:MAC + Timeバージョンは、ランダムバージョンの20倍高速です。

時間ベースのバリアント(イーサネットアドレスとタイムスタンプ)ははるかに高速です-ほぼランダムベースの20倍の速度デフォルトのバリアント-毎秒約500万のUUIDを生成します。

4
Basil Bourque

Jdk 1.7.0_40で実行されるjunitテスト:

package org.corba.util;

import org.junit.Test;
import org.springframework.util.StopWatch;

import Java.util.UUID;

/**
 * Test of performance of Java's UUID generation
 * @author Corba Da Geek
 * Date: 1/6/14
 * Time: 3:48 PM
 */
public class TestRandomUUID {
    private static final int ITERATIONS = 1000000;

    @Test
    public void testRandomUUID() throws Exception {
        // Set up data
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // Run test
        for (int i = 0; i < ITERATIONS; i++)
            UUID.randomUUID();

        // Check results
        stopWatch.stop();
        final long totalTimeMillis = stopWatch.getTotalTimeMillis();
        System.out.println("Number of milliseconds: " + totalTimeMillis + " for " + ITERATIONS + " iterations.");
        System.out.println(String.format("Average time per iteration: %.7f ms", (float)totalTimeMillis/ITERATIONS));
    }
}

そして私のi5ラップトップでの結果は:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.corba.util.TestRandomUUID
Number of milliseconds: 677 for 1000000 iterations.
Average time per iteration: 0.0006770 ms
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.746 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

呼び出しごとに0.0006770ミリ秒

1
CorbaTheGeek

私は他の人と同じテストを行いました私の結果はUUID生成ごとに300 NANOsecondsに似ています。結果は、i7クアッドコアWIN7 64 PCでの結果です。 jdk1.7.0_67とjdk1.8.0_40 64ビットJVMを試してみました。

私は困惑しているようです。私の結果は他のすべてのものとはとても異なります...しかし、乱数を生成するための1 msはLOTのように見えました!

public static void main(String[] args) throws Exception {
    long start = System.nanoTime();
    int loops = 1000000; // One million.

    long foo = 0;

    for (int i = 0; i < loops; i++) {
        UUID uuid = Java.util.UUID.randomUUID();

        //this is just to make sure there isn't some kind of optimization
        //that would prevent the actual generation
        foo += (uuid.getLeastSignificantBits()
                + uuid.getMostSignificantBits());
    }

    long stop = System.nanoTime();

    long elapsed = (stop - start);

    System.out.println(String.format("UUIDs              : %,d", loops));
    System.out.println(String.format("Total time (ns)    : %,d", elapsed));
    System.out.println(String.format("Time per UUID (ns) : %,d", (elapsed / loops)));

    System.out.println();
    System.out.println(foo);
}

出力 :

UUIDs              : 1 000 000
Total time (ns)    : 320 715 288
Time per UUID (ns) : 320

5372630452959404665
0
yannick1976