セッション情報用にランダムな UUID を生成する Web ベースの Java アプリケーションがあります。私たちのテスターの 1 人は、彼自身のプロファイリングに基づいて UUID を生成するのに最大 350 ミリ秒かかると主張していますが、私はまだ彼の結果を再現できていません。彼はこの記事http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.htmlを指摘して、結果を裏付けています。Java 6 または Java 7 アプリケーションのいずれかで、Java の組み込み UUID 生成機能を使用してこの制限に遭遇した人が他にいるかどうかを確認したかったのです。
7 に答える
UUID のランダムな形式は、通常、「暗号強度」の乱数のソースを使用します。
(そうでなければ、いわゆるランダムUUIDが予測可能になり、特定のUUIDが再発行される可能性が心配なレベルにまで増加する可能性があります。別の回答が示唆するように、UUID
コンストラクターに高速(ただし弱い)PRNGを提供できます。しかし、それは悪い考えです。)
一般的な暗号強度の乱数ジェネレーターは、アプリケーションの外部にあるエントロピーのソースを使用します。ハードウェアの乱数ジェネレーターの可能性もありますが、より一般的には、通常の操作でオペレーティング システムによって収集される蓄積された "乱数" です。問題は、エントロピーのソースにレート制限があることです。一定期間にわたってそのレートを超えると、ソースを排出できます。次に何が起こるかはシステムに依存しますが、一部のシステムでは、エントロピーを読み取るためのシステムコールが停止します...さらに利用可能になるまで。
それがあなたのクライアントのシステムで起こっていることだと思います。(仮想マシンでは珍しくありません...)
(Linux システム用の) ハックな回避策の 1 つは、rngd
デーモンをインストールし、適切な疑似乱数ジェネレーターを使用してエントロピー プールを「補充」するように構成することです。セキュリティの専門家は次のように指摘します。
- これは UUID ジェネレーターのランダム性に影響し、
- エントロピー プールは他のセキュリティ関連のものに使用されるため、疑わしいソースから追加すると、システム上の多くのセキュリティが弱体化します。
このハッキングが実際にどれほど安全かはわかりません。
低速な乱数生成に関する別の Q&A を次に示します。
私はそれをテストしました
for (;;) {
long t0 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
UUID.randomUUID();
}
System.out.println(System.currentTimeMillis() - t0);
}
私のPCでは約1100ミリ秒で、かなり遅いです。UUID.randomUUID() は内部で SecureRandom を使用します。高速化するために、通常の java.util.Random を使用できます。
Random r = new Random();
for (;;) {
..
new UUID(r.nextLong(), r.nextLong());
それは〜80ミリ秒です
スレッドの数は、UUID の生成のパフォーマンスに大きな影響を与えます。SecureRandom#nextBytes(byte[]
これは、 の乱数を生成するの実装を見ることで説明できますUUID.randomUUID()
。
synchronized public void nextBytes(byte[] bytes) {
secureRandomSpi.engineNextBytes(bytes);
}
nextBytes
これはsynchronized
、異なるスレッドからアクセスされると、パフォーマンスが大幅に低下する原因となります。
4 の代わりにバージョン 1 を使用
バージョン 1 型の UUID を使用するのはどうですか?
バージョン 1は、MAC アドレスと現在の時間 (「空間と時間」) に基づいています。バージョン 4 よりも衝突の可能性がはるかに低くなります。
バージョン 4は、暗号的に強力なランダム ジェネレーターを使用して乱数から完全に生成されることに基づいています。
Oracle JVM はバージョン 1 ジェネレーターを提供していません。これは、明らかにセキュリティとプライバシーの問題のためです。JVM は、ホスト マシンの MAC アドレスへのアクセスを提供しません。
JUG図書館
バージョン 1 の UUID と他のバージョンを提供する、少なくとも 1 つのサードパーティ ライブラリが利用可能です: JUG – Java UUID Generator。彼らは、Java 6 で導入された機能により、MAC アドレスにアクセスできるようになったと言います。
テスト結果: 20x
2010 年の記事Java UUID Generator (JUG) の詳細、パフォーマンスに関する言葉 で、Java UUID Generator バージョン 3を使用したテスト結果によるパフォーマンスの説明を参照してください。Tatu Saloranta は、彼の MacBook でさまざまな種類の UUID をテストしました。
結論: MAC+Time バージョンは、ランダム バージョンの 20 倍高速です。
時間ベースのバリアント (イーサネット アドレスとタイムスタンプ) ははるかに高速です。ランダム ベースのデフォルトのバリアントのほぼ 20 倍の速さで、1 秒あたり約 500 万の UUID を生成します。
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 ミリ秒。
他のテストと同じテストを行ったところ、結果は UUID 生成ごとに 300 NANOseconds のようになりました。結果は、i7 クアッドコア WIN7 64 PC でのものです。jdk1.7.0_67 と jdk1.8.0_40 64 ビット JVM を試しました。
私の結果は他のすべての結果とは非常に異なっているので、ちょっと困惑しています...しかし、乱数を生成するための1ミリ秒はかなりのようでした!
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