🎲

Go1.22で追加される新しい乱数パッケージmath/rand/v2の紹介

2023/12/11に公開

Goは2009年にリリースされて以来、これまで数多くの標準パッケージが実装されてきましたが、リリースから時間が経つにつれて様々な問題が発見されてきました。それらの問題はプログラムの外部から見える振る舞いを変えないようであれば修正が行われてきましたが、振る舞いを変える場合、後方互換性を維持するために非推奨にして使用しないように勧告したり、既存の実装を残したまま拡張したりしながら問題を回避してきました。

最近では、既存のパッケージを残しつつ、新しくv2パッケージとして提供しようという動きが出てきており、例えば既存の標準パッケージであるencoding/jsonの様々な問題点を解消するために、そのv2パッケージencoding/json/v2が提案されていることをご存じの方も多いと思います。実はGo1.22ではencoding/json/v2よりも一足先に、パッケージmath/randにv2パッケージが導入されようとしており、v2パッケージの先駆けとなる見込みです。

Go1.22のリリースはまだ先の話で、math/rand/v2にも変更が加えられている最中ですが、本記事執筆時点でどのような新機能や変更が加えられる予定か、ご紹介していきます(※疑似乱数生成器の名前がいくつか出てきますが、この記事では詳細なアルゴリズムについては触れません)。

この記事は以下のProposalを元に執筆しています。

https://github.com/golang/go/issues/61716

またv2前後のパッケージで提供される機能の差分はこちらのパッケージドキュメントが参考になります。
https://pkg.go.dev/math/rand@master
https://pkg.go.dev/math/rand/v2@master

Rand.Read, Readの削除

メソッドRand.Readおよび関数Readが削除されました。パッケージmath/randRandを乱数のシーケンスを生成するために用いるのは適切ではなく、パッケージcrypto/randReadを使用するようにするために、Go1.20で非推奨になっていました。今回の変更ではこれらの関数が削除された形となります。

https://go-review.googlesource.com/c/go/+/502497
https://github.com/golang/go/issues/20661

Source.Seed, Rand.Seed、Seedの削除

インターフェースSourceのメソッドSeedが削除されました。削除された理由として、このSeedは引数に単一のint64しか受け付けず、限られた乱数生成器しかRand初期化時のSourceとして適さなくなるためです。シードをどのように決めるかはSourceの実装にゆだねられます。

また、メソッドRand.Seedも削除されました。シードを初期化するには乱数生成器自体を再度初期化する必要があります。

さらに関数Seedも削除されました。SeedはGo1.20時点で元々非推奨にされていたため、今回削除されています。

https://go-review.googlesource.com/c/go/+/502498

Sourceの変更

インターフェースSourceのメソッドInt64() int64Uint64() uint64に変更されます。

https://go-review.googlesource.com/c/go/+/502499

Source64の削除

インターフェースSourceの変更によりSource64と同等のインターフェースを提供されるようになったためSource64は削除されます。

Float32, Float64の実装の変更

Go1.21までの実装では、浮動小数点数の乱数を生成するために使用していた整数の乱数生成器が生成する乱数の値域の都合により、例外的な乱数が生成されたときにリトライする処理が行われていましたが。しかし、使用する整数の乱数生成器の値域が変わったため、リトライ処理を行わないで済むようになりました。

https://go-review.googlesource.com/c/go/+/502503

Rand.ExpFloat64, Rand.NormFloat64の偏りの修正

指数分布に基づく乱数を生成するメソッドRand.ExpFloat64、および正規分布をに基づく乱数を生成するメソッドRand.NormFloat64の生成する乱数に偏りがあったため、修正されました。

https://go-review.googlesource.com/c/go/+/516275
https://github.com/golang/go/discussions/60751#discussioncomment-6167206

Rand.Permの内部実装でRand.Shuffleが使われるようになる

メソッドRand.Permの内部実装において、メソッドRand.Shuffleを使用するほうが既存の実装よりも処理速度が速く、実装もシンプルになったため、変更が加えられました。

https://go-review.googlesource.com/c/go/+/502504

Intn, Int31, Int31n, Int63, Int64nをリネーム

関数Intn, Int31, Int31n, Int63, Int64nはそれぞれIntN, Int32, Int32N, Int64, Int64Nにリネームされました。

https://go-review.googlesource.com/c/go/+/516857

Uint32, Uint32N, Uint64, Uint64N, Uint, UintNの追加

符号なし整数の乱数を生成する関数Uint32, Uint32N, Uint64, Uint64N, Uint, UintNが追加されました。また、同名のメソッドとして構造体Randにも追加されます。

https://go-review.googlesource.com/c/go/+/502500

Nの追加

任意の整数型の乱数を生成する関数Nが追加されます。以下の整数型を型引数として取るGenericsで実装されており、underlying typeも受け付けるため、例えばtime.Durationの乱数を生成することもできます。

type intType interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

https://go-review.googlesource.com/c/go/+/502500

N, IntN, UintNで使用する新しいアルゴリズム

関数N, IntN, UintNでは、Daniel Lemire氏が発表したアルゴリズムを使用します。

https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/

新しい乱数生成器PCG-DXSM

新しい乱数生成器PCG-DXSMが追加されます。この乱数生成器はO’Neill氏がNumpyで考案したもので、高速でシンプルでかつ統計的にランダム性が保証されるのが特徴です。DXSMは元のNumpyのコードによると、Double XorShift Multiplyの略らしいです。

https://go-review.googlesource.com/c/go/+/502505
https://dotat.at/@/2023-06-21-pcg64-dxsm.html

NewSourceの削除

関数NewSourceが削除されました。また、NewSourceで初期化されていた事実上のデフォルトの乱数生成器rngSourceも削除されました。PCG-DXSMが導入された際に、rngSourceはPlan9が作られた時代からある実装で、性能としては疑似乱数生成器の最先端の研究とは程遠く、代わりにPCGを使ってほしいと述べられていました。

https://go-review.googlesource.com/c/go/+/502506

新しい乱数生成器ChaCha8

新しい乱数生成器ChaCha8が追加されます。この乱数生成器はChaCha8ストリーム暗号をベースとした暗号学的疑似乱数を生成します。暗号学的疑似乱数の生成器はセキュリティ的に強固な分、暗号学的ではない通常の疑似乱数生成器に比べると乱数を生成する速度は一般的に遅くなりますが、128ビットのベクトル演算が可能なCPUアーキテクチャにおいては、PCGとほぼ同等の速度を期待できることがベンチマークから分かっています。

https://go-review.googlesource.com/c/go/+/546020
https://cr.yp.to/chacha/chacha-20080128.pdf

グローバルな乱数生成器としてChaCha8を採用

依然としてパッケージグローバルな乱数生成器を使用するケースが多いため、これまではwyrandが用いられていました、セキュリティ上安全なChaCha8が用いられるようになりました。なおGo1.22からはmath/rand/v2だけではなく、math/randのグローバルな乱数生成器としてChaCha8が用いられます。

https://go-review.googlesource.com/c/go/+/516860

Discussion