Go1.22で追加される新しい乱数パッケージmath/rand/v2の紹介
Goは2009年にリリースされて以来、これまで数多くの標準パッケージが実装されてきましたが、リリースから時間が経つにつれて様々な問題が発見されてきました。それらの問題はプログラムの外部から見える振る舞いを変えないようであれば修正が行われてきましたが、振る舞いを変える場合、後方互換性を維持するために非推奨にして使用しないように勧告したり、既存の実装を残したまま拡張したりしながら問題を回避してきました。
最近では、既存のパッケージを残しつつ、新しくv2パッケージとして提供しようという動きが出てきており、例えば既存の標準パッケージであるencoding/jsonの様々な問題点を解消するために、そのv2パッケージencoding/json/v2が提案されていることをご存じの方も多いと思います。実はGo1.22ではencoding/json/v2よりも一足先に、パッケージmath/randにv2パッケージが導入されようとしており、v2パッケージの先駆けとなる見込みです。
Go1.22のリリースはまだ先の話で、math/rand/v2にも変更が加えられている最中ですが、本記事執筆時点でどのような新機能や変更が加えられる予定か、ご紹介していきます(※疑似乱数生成器の名前がいくつか出てきますが、この記事では詳細なアルゴリズムについては触れません)。
この記事は以下のProposalを元に執筆しています。
またv2前後のパッケージで提供される機能の差分はこちらのパッケージドキュメントが参考になります。
Rand.Read, Readの削除
メソッドRand.Read
および関数Read
が削除されました。パッケージmath/rand
のRand
を乱数のシーケンスを生成するために用いるのは適切ではなく、パッケージcrypto/rand
のRead
を使用するようにするために、Go1.20で非推奨になっていました。今回の変更ではこれらの関数が削除された形となります。
Source.Seed, Rand.Seed、Seedの削除
インターフェースSource
のメソッドSeed
が削除されました。削除された理由として、このSeed
は引数に単一のint64
しか受け付けず、限られた乱数生成器しかRand
初期化時のSource
として適さなくなるためです。シードをどのように決めるかはSource
の実装にゆだねられます。
また、メソッドRand.Seed
も削除されました。シードを初期化するには乱数生成器自体を再度初期化する必要があります。
さらに関数Seed
も削除されました。Seed
はGo1.20時点で元々非推奨にされていたため、今回削除されています。
Sourceの変更
インターフェースSource
のメソッドInt64() int64
がUint64() uint64
に変更されます。
Source64の削除
インターフェースSource
の変更によりSource64
と同等のインターフェースを提供されるようになったためSource64
は削除されます。
Float32, Float64の実装の変更
Go1.21までの実装では、浮動小数点数の乱数を生成するために使用していた整数の乱数生成器が生成する乱数の値域の都合により、例外的な乱数が生成されたときにリトライする処理が行われていましたが。しかし、使用する整数の乱数生成器の値域が変わったため、リトライ処理を行わないで済むようになりました。
Rand.ExpFloat64, Rand.NormFloat64の偏りの修正
指数分布に基づく乱数を生成するメソッドRand.ExpFloat64
、および正規分布をに基づく乱数を生成するメソッドRand.NormFloat64
の生成する乱数に偏りがあったため、修正されました。
Rand.Permの内部実装でRand.Shuffleが使われるようになる
メソッドRand.Perm
の内部実装において、メソッドRand.Shuffle
を使用するほうが既存の実装よりも処理速度が速く、実装もシンプルになったため、変更が加えられました。
Intn, Int31, Int31n, Int63, Int64nをリネーム
関数Intn, Int31, Int31n, Int63, Int64n
はそれぞれIntN, Int32, Int32N, Int64, Int64N
にリネームされました。
Uint32, Uint32N, Uint64, Uint64N, Uint, UintNの追加
符号なし整数の乱数を生成する関数Uint32, Uint32N, Uint64, Uint64N, Uint, UintN
が追加されました。また、同名のメソッドとして構造体Rand
にも追加されます。
Nの追加
任意の整数型の乱数を生成する関数N
が追加されます。以下の整数型を型引数として取るGenericsで実装されており、underlying typeも受け付けるため、例えばtime.Duration
の乱数を生成することもできます。
type intType interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
N, IntN, UintNで使用する新しいアルゴリズム
関数N, IntN, UintN
では、Daniel Lemire氏が発表したアルゴリズムを使用します。
新しい乱数生成器PCG-DXSM
新しい乱数生成器PCG-DXSMが追加されます。この乱数生成器はO’Neill氏がNumpyで考案したもので、高速でシンプルでかつ統計的にランダム性が保証されるのが特徴です。DXSMは元のNumpyのコードによると、Double XorShift Multiplyの略らしいです。
NewSourceの削除
関数NewSource
が削除されました。また、NewSource
で初期化されていた事実上のデフォルトの乱数生成器rngSource
も削除されました。PCG-DXSMが導入された際に、rngSource
はPlan9が作られた時代からある実装で、性能としては疑似乱数生成器の最先端の研究とは程遠く、代わりにPCG
を使ってほしいと述べられていました。
新しい乱数生成器ChaCha8
新しい乱数生成器ChaCha8が追加されます。この乱数生成器はChaCha8ストリーム暗号をベースとした暗号学的疑似乱数を生成します。暗号学的疑似乱数の生成器はセキュリティ的に強固な分、暗号学的ではない通常の疑似乱数生成器に比べると乱数を生成する速度は一般的に遅くなりますが、128ビットのベクトル演算が可能なCPUアーキテクチャにおいては、PCG
とほぼ同等の速度を期待できることがベンチマークから分かっています。
グローバルな乱数生成器としてChaCha8を採用
依然としてパッケージグローバルな乱数生成器を使用するケースが多いため、これまではwyrandが用いられていました、セキュリティ上安全なChaCha8が用いられるようになりました。なおGo1.22からはmath/rand/v2だけではなく、math/randのグローバルな乱数生成器としてChaCha8が用いられます。
Discussion