初めに
オークファンの開発部に2021年に新卒入社した@isodaです。
業務でGoの乱数生成を使用する機会があったので、いろいろ調べてみました。
Goの乱数生成について
GOの乱数生成には標準パッケージで、math/randとcrypto/randの2通りの方法が存在しています。
math/rand
- 特徴
- 乱数の元になる、シード値が必要
- セキュリティ性が低い
crypto/rand
- 特徴
- 乱数の元になる、シード値が必要ない
- 暗号的に安全な乱数ジェネレーター
- LinuxおよびFreeBSDでは、Readerは利用可能な場合はgetrandomを使用し、それ以外の場合は/dev/urandomを使用します
ベンチマーク用のプログラム作成
Goでは標準パッケージでベンチマーク用の機能があるらしく、そちらを使用しました
今回はmath/randとcrypto/randでそれぞれ、0~100の乱数と0~9223372036854775807(Int64の最大値)で乱数を作成する関数を作成しました
func BenchmarkAppend_MathRand100(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
rand.Seed(time.Now().UnixNano())
rand.Intn(100)
}
}
func BenchmarkAppend_MathRandMax(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
rand.Seed(time.Now().UnixNano())
rand.Intn(9223372036854775807)
}
}
func BenchmarkAppend_CryptoRand_100(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
rand.Int(rand.Reader, big.NewInt(100))
}
}
func BenchmarkAppend_CryptoRand_Max(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
rand.Int(rand.Reader, big.NewInt(9223372036854775807))
}
}
ローカル環境で測定
ローカルマシンについて
- MacBook Pro
- バージョン:10.15.7
- CPU:2.8GHz クアッドコアIntel Core i7
- メモリ:16GB 2133MHz
go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: rand
BenchmarkAppend_CryptoRand_100-8 7543069 149 ns/op 56 B/op 4 allocs/op
BenchmarkAppend_CryptoRand_Max-8 8180403 143 ns/op 56 B/op 4 allocs/op
BenchmarkAppend_MathRand100-8 161563 7044 ns/op 0 B/op 0 allocs/op
BenchmarkAppend_MathRandMax-8 163651 6986 ns/op 0 B/op 0 allocs/op
PASS
ok rand 5.328s
各値について
この値の見方としては、左から
関数名
実行回数
1回あたりの時間
1回あたりのアロケーションで確保した容量
1回あたりのアロケーション回数
思ったこと
- 0~100までの乱数と0~Int64の最大値の乱数では、実行速度にあまり差が見られませんでした。
値の大きい乱数を作る方が遅くなるのかなと思っていたので、意外でした。 - math/randの方が早いかなと思っていたのですが、crypto/randの方が5倍くらい早く処理が終わっています。
- math/randはメモリアロケーションをおこなわずに、crypto/randはメモリアロケーションを行っている様です、なのでcrypto/randの実行速度はメモリの使用状況や性能に大きく左右されそうです。
EC2上で測定
今回検証したEC2の構成
- EC2
- AMI:Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type
- インスタンスタイプ:t3.micro
- vCPU:2
- メモリ:1GB
- クレジット仕様
- unlimited
go test -bench . -benchmem
goos: linux
goarch: amd64
BenchmarkAppend_CryptoRand_100-2 825255 1426 ns/op 56 B/op 4 allocs/op
BenchmarkAppend_CryptoRand_Max-2 1000000 1125 ns/op 56 B/op 4 allocs/op
BenchmarkAppend_MathRand100-2 117762 10070 ns/op 0 B/op 0 allocs/op
BenchmarkAppend_MathRandMax-2 119493 10047 ns/op 0 B/op 0 allocs/op
PASS
ok _/home/ec2-user/rand 4.927s
思ったこと
- ローカルで実行した際よりも、crypto/randの時間が10倍ほど遅くなっています、やはりメモリのスペックに大きく左右される様です
- math/randはローカルと比べて、1.3倍ほど遅くなっている様です、crypto/randほどの差は出ない様です
- math/randとcrypto/randで比較すると、やはりcrypto/randの方が8~9倍近く早く処理が完了している様です
まとめ
より安全な乱数を作成できるcrypto/randの方が今回の場合では処理が早いことがわかりました。
しかしcrypto/randはメモリアロケーションを行う為、動作させる環境などによって考慮が必要です。