Goの乱数生成について調べてみた（ベンチマーク）

# 初めに
オークファンの開発部に2021年に新卒入社した@isodaです。
業務でGoの乱数生成を使用する機会があったので、いろいろ調べてみました。

# Goの乱数生成について

GOの乱数生成には標準パッケージで、math/randとcrypto/randの2通りの方法が存在しています。


## math/rand
https://pkg.go.dev/math/rand

- 特徴
    - 乱数の元になる、シード値が必要
    - セキュリティ性が低い

## crypto/rand
https://pkg.go.dev/crypto/rand

- 特徴
    - 乱数の元になる、シード値が必要ない
    - 暗号的に安全な乱数ジェネレーター
    - LinuxおよびFreeBSDでは、Readerは利用可能な場合はgetrandomを使用し、それ以外の場合は/dev/urandomを使用します

# ベンチマーク用のプログラム作成
Goでは標準パッケージでベンチマーク用の機能があるらしく、そちらを使用しました

https://pkg.go.dev/testing#hdr-Benchmarks

今回はmath/randとcrypto/randでそれぞれ、0~100の乱数と0~9223372036854775807(Int64の最大値)で乱数を作成する関数を作成しました

```go
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回あたりのアロケーション回数

## 思ったこと
1. 0~100までの乱数と0~Int64の最大値の乱数では、実行速度にあまり差が見られませんでした。
値の大きい乱数を作る方が遅くなるのかなと思っていたので、意外でした。
2. math/randの方が早いかなと思っていたのですが、crypto/randの方が5倍くらい早く処理が終わっています。
3. 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
```

## 思ったこと
1. ローカルで実行した際よりも、crypto/randの時間が10倍ほど遅くなっています、やはりメモリのスペックに大きく左右される様です
2. math/randはローカルと比べて、1.3倍ほど遅くなっている様です、crypto/randほどの差は出ない様です
3. math/randとcrypto/randで比較すると、やはりcrypto/randの方が8~9倍近く早く処理が完了している様です

# まとめ
より安全な乱数を作成できるcrypto/randの方が今回の場合では処理が早いことがわかりました。
しかしcrypto/randはメモリアロケーションを行う為、動作させる環境などによって考慮が必要です。