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