golang 標準の math/rand
は goroutine safe ではあるものの、単にロックを取っているだけで非常に遅い。そこで、高速なものを書いてみた。
ベンチマークの結果は以下の通り。単一の goroutine だと遅いが、64並列に設定した場合には逆転している。
仕組みとしては乱数生成機を複数作って sync.Pool
で使いまわしている。
というわけでロックを取るのと等価ではない。が、とにかく乱数がほしいのであればこれで問題ないはず。
標準では math/rand
の乱数生成機を内部的に利用しているが、rand.Source
interface を実装しているものであれば他の乱数生成機に差し替えることが可能。math/rand
は速度的にも品質的にも優れているとはいえないので、適当に差し替えるのが無難。
sync.Pool
は手軽にそこそこ高速化できてよいが、ほんとは thread local storage 的なものがほしい。golang の runtime の実装では、スレッドを表現する M
構造体の中に乱数のステートを保持しており、これを使って高速に乱数生成する runtime.fastrand
という関数が存在する。こういうアプローチの実装がユーザでもできたらうれしい。ただ、golang の世界では基本的にスレッドを意識しないようになっているし、thread local storage を操作する部分では goroutine のプリエンプションを明示的に無効化する必要があったりして難しいので、シンプルな golang の方向性とは合わないのかもしれない。