SplitMix64徹底解説:高速な64ビットPRNGの仕組みと用途、暗号用途での注意点

はじめに — SplitMix64とは何か

SplitMix64は、64ビット幅の非常に高速で単純な擬似乱数生成器(PRNG)です。内部状態を64ビットで持ち、状態を定数で加算(増分)する単純な操作と、そこから出力を得るための「ミキシング関数(mix)」を組み合わせた構造をとります。設計は高速性と実用的な統計特性を重視しており、乱数の初期化(シード生成)や汎用の乱数源として幅広く利用されています。ただし暗号学的安全性はなく、暗号用には適しません。

アルゴリズムの核心

代表的な実装は次のようになります(C風の擬似コード)。ポイントは「状態を一定の奇数定数で加算」し、その結果に対してビットごとのXORと右シフト、乗算を複数回組み合わせてビットを拡散(mix)することです。

uint64_t state;

uint64_t next() {
  uint64_t z = (state += 0x9E3779B97F4A7C15ULL);
  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
  z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
  return z ^ (z >> 31);
}

ここで使われる主な定数は以下の役割を持ちます。

  • 0x9E3779B97F4A7C15:状態を進める「増分(step/gamma)」で、黄金比に由来する定数が用いられることが多く、奇数であるためモジュロ2^64空間で全周期(period 2^64)を確保します。
  • 0xBF58476D1CE4E5B9、0x94D049BB133111EB:ミキシングのための乗数。奇数であり、乗算は2^64上で可逆(乗数が奇数なら乗法逆元が存在)な操作です。

設計思想と性質

  • 周期:内部状態は64ビットで単純に加算して進むため、増分が奇数なら内部状態は周期2^64を持ちます。ミキシング関数は基本的に内部状態から出力への一様な写像を目指すもので、出力系列も実用上は長周期に相当します。
  • 速度:演算は加算・XOR・定数乗算・シフトのみであり、整数演算に最適化されたCPU上では非常に高速です。そのためシミュレーションや数値計算、乱数のシード生成などで好んで使われます。
  • 統計品質:単純な線形生成器に比べビット間の拡散(混合)が良く、一般的なベンチマークや統計テストスイートに対して良好な結果を示します。実装によってはTestU01やPractRandなどで良好なスコアを得ている報告があります(後述の参考文献参照)。
  • 可逆性と暗号性の欠如:上記のミキシングに使われる演算は可逆性を持つ(乗算に逆元が存在し、XOR/シフトの組合せも逆変換が可能)ため、出力から内部状態を逆算することが比較的容易です。したがって暗号学的に安全な乱数生成器ではありません。

なぜ「Split」と「Mix」か

名前の由来は「split(分割)」と「mix(混合)」の組み合わせで、SplitMixは並列処理やストリーム分割(splittable)を意識した設計思想と、出力を強く混合する(mix)という2つの側面を表しています。実際、JavaのSplittableRandomなどの実装では、ストリームを分割して別々の乱数源を得るための手法として同様の考え方が使われています(OpenJDKの実装では類似のミキシング関数が登場します)。

用途と実装上の注意点

  • 用途:シミュレーション、乱数の初期シードやハッシュ化、並列計算環境で各スレッドへ異なるシードを与えるためのジェネレータ、非暗号目的のゲームなど。
  • 初期化シード:シードは任意の64ビット値でよく、0を許容します。ただし複数ストリームを作る場合や同時実行環境ではシードの衝突・相関に注意して、十分に異なる初期状態を与えることが重要です。
  • 増分の選び方:増分(gamma)は奇数であることが必要です。OpenJDKのSplittableRandomの実装では、増分を選ぶ際にビットの分布(ポピュレーションカウントなど)に基づく簡易なチェックを行い、良好なビット分散が得られるようにしています。
  • 並列性:単純に状態を異なるシードで初期化すれば複数ストリームを生成できますが、ストリーム間の相関を避けるためにはシード生成方法や増分の選択に注意が必要です。SplittableRandomのように「分割(split)」操作を設けて、元の生成器から新たな増分・シードを生成する方法が推奨されるケースもあります。

統計検定と評価

SplitMix64は実装次第でTestU01やPractRandなどで良い結果を示すことが知られています。例えば、SplitMix64の有名な実装(上で示したもの)は広く試験され、実用上十分な乱数性を示しています。ただし、統計テストは乱数生成器の適不適を完全に決定するものではなく、特定の用途(高精度なモンテカルロ法や並列アルゴリズム)では更なる評価が必要です。

また、可逆的なミキシング構造のため特定の解析(逆解析)に弱く、暗号学的な用途や攻撃に対する耐性が必要な場合はCrypto-secureなジェネレータ(例:ChaCha20ベースのDRBGやOS提供のCSPRNG)を用いるべきです。

実装上の良い慣行

  • シード管理:テスト環境と本番環境でシードの扱いを明確に分離する。再現性が必要な実験ではシードを固定し、公開されるコードではデフォルトで安全な乱数源からシードを取る。
  • 多重スレッド環境:各スレッドで単一の共有ジェネレータにアクセスしてロックするより、各スレッドに独立なジェネレータを持たせる(ただしシードの衝突に注意)方が並列性能は良い。
  • テスト:自分の利用ケースに対して、簡易的な分布チェック(平均・分散・自己相関)や、必要ならPractRand/TestU01による検証を行う。
  • 暗号用途の回避:パスワード生成・キー生成など機密性が要求される用途には使用しない。

代替と比較

  • Xorshift/xoshiroシリーズ:更に高速で良好な統計特性を持つ直交的な乱数生成器群。並列ストリーム生成やビットレベルの性質に優れる実装が多く存在します。
  • PCG(Permuted Congruential Generator):似た設計哲学で可搬性が高く、良好な統計性を示すことが多い。ストリーム分割機能も提供されている。
  • 暗号学的PRNG:ChaCha20やAES-CTRベースのPRNGは暗号的セキュリティが必要な用途に推奨される。

まとめ

SplitMix64は「非常に速い」「実用上十分な統計特性」「実装が簡単」という特徴を持つ汎用PRNGです。シミュレーション、シード生成、並列環境でのストリーム生成用などの用途に向いていますが、出力から内部状態を逆算できるなど暗号学的安全性は皆無です。用途に応じて代替アルゴリズムと比較しながら採用を検討してください。

参考文献