Java17にjava.util.randomパッケージが追加されたので改めて乱数生成器について調査してみることにしました。
ちなみに本記事では特にどの乱数生成器がよいというような検証はしません。(用途によって向き不向きがあるはずなので)
ベンチマーク環境
- OS
- Windows10Pro
- WSL1
- Ubuntu20.04LTS
- WSL1
- Windows10Pro
- CPU
- Core i7-10700 (8core 16thread)
- Memory
- 32GB
- Java
- OpenJDK17
- Apache Maven
- 3.8.3
コード
調査したアルゴリズムについて
各アルゴリズムについてシングルスレッドの場合とマルチスレッドの場合でテストしています。
マルチスレッドは4スレッドとし、実行したPCのCPUコア数内に収めています。
※Java16までを使う場合の参考としてApache Commons RNGの各アルゴリズムのテストも入っていますが、本記事では割愛。
※参考用にjava.util.Randomを含めているものの、品質が悪いため実環境では使用しません。
- シングルスレッド
- java.util.Random
- java.security.SecureRandom (NativePRNGNonBlocking)
- java.util.concurrent.ThreadLocalRandom
- java.util.random.RandomGenerator (L64X128MixRandom)
- java.util.random.RandomGenerator (Xoroshiro128PlusPlus)
- マルチスレッド
- java.util.Random
- java.security.SecureRandom (NativePRNGNonBlocking)
- java.util.concurrent.ThreadLocalRandom
- java.util.random.RandomGenerator (L64X128MixRandom) ThreadLocal
- java.util.random.RandomGenerator (Xoroshiro128PlusPlus) ThreadLocal
マルチスレッドで後ろにThreadLocalとついているものはRandomGeneratorがスレッドセーフではないため、スレッド毎にインスタンスを作成するようにしています。
結果
シングルスレッド
Benchmark Mode Cnt Score Error Units
RandomBenchmarkSingleThread.jdkRandom(1) thrpt 5 131639688.221 ± 832868.840 ops/s
RandomBenchmarkSingleThread.secureRandom(2) thrpt 5 2656059.305 ± 14842.602 ops/s
RandomBenchmarkSingleThread.threadLocalRandom(3) thrpt 5 501922756.671 ± 2784109.670 ops/s
RandomBenchmarkSingleThread.jdk17L64X128MixRandom(4) thrpt 5 331029209.781 ± 3377450.451 ops/s
RandomBenchmarkSingleThread.jdk17Xoroshiro128ppRandom(5) thrpt 5 439135306.643 ± 4206646.624 ops/s
マルチスレッド
Benchmark Mode Cnt Score Error Units
RandomBenchmarkMultiThread.jdkRandom(1) thrpt 5 15938216.638 ± 1536272.415 ops/s
RandomBenchmarkMultiThread.secureRandom(2) thrpt 5 1835105.725 ± 10398.678 ops/s
RandomBenchmarkMultiThread.threadLocalRandom(3) thrpt 5 1915947749.858 ± 96167030.714 ops/s
RandomBenchmarkMultiThread.threadLocalJdk17L64X128MixRandom(4) thrpt 5 714011780.505 ± 25031734.867 ops/s
RandomBenchmarkMultiThread.threadLocalJdk17Xoroshiro128ppRandom(5) thrpt 5 1035939838.978 ± 77871089.459 ops/s
考察
RandomやSecureRandomはスレッドセーフではあるものの内部で同期化されているのでマルチスレッドで性能が悪化しています。
また、Java17で追加された乱数生成器はスレッドセーフではないためThreadLocalでラップして実験しましたが、4スレッド時に2倍強程度の性能なのでそこまで性能が伸びませんでした。(マルチスレッドで性能が伸びないのはThreadLocalの性能のせいかもしれません)
性能面では以前から存在しているThreadLocalRandomが最も高速でかつマルチスレッド時にほぼスレッド数に比例した性能が出ていることがわかります。
個人的には、暗号やセッションキーの生成等ではSecureRandom。それ以外は通常ThreadLocalRandom。長い周期が必要な場合(ThreadLocalRandomは2の64乗周期でそれほど長くない)に適宜アルゴリズムを選んで使うといったところでしょうか。
ちなみにJava17で利用できるアルゴリズムや周期等の情報はJavadocのjava.util.randomパッケージに載っています。