[golang] ベンチマークテストの書き方

# 目的
golang におけるベンチマークテストの書き方を検討する。

# 考察
https://www.amazon.co.jp/-/en/Teiva-Harsanyi/dp/4295017531
> 次のパターンに従うのがベストプラクティスになります。
> 1. 各ループ処理で、結果をローカル変数(ベンチマーク関数でローカル)に代入します
> 2. 最終の結果をグローバル変数に代入します

上記の方法がベストプラクティスです。

ここで気になるのが、複数のベンチマークテストを比較したいとき、同じグローバル変数を使っていいのかです。
下記で検証しましたが、グローバル変数はそれぞれのベンチマークテストで用意するのがいいかもしれません。

前提として、個別に実施したベンチマークテストの一回目を本来のタイムと考えます。

個別に行ったベンチマークテストを見ると、両方の関数とも2回目以降高速になっていて、`PraMap2` 関数のタイムの減少は顕著です。
そして、二つの関数のベンチマークテストを同時に実施した結果を見ると、グローバル変数を共有して且つ、`PraMap2`が後に実施された場合、`PraMap2`はかなり高速化されています(`448045973 ns/op`の部分です)。

グローバル変数は最後に格納するだけで、繰り返し処理とは何ら関係なさそうですが、ベンチマークテストのタイムに影響を及ぼします。理由は明確ではありませんが、CPUのキャッシュラインによるものかもしれません。




# ベンチマークテストの対象となる関数
``` pra_map.go
package main

import "strconv"

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
// 初期サイズを与えて map を作成する
func PraMap1() map[string]int {
	m := make(map[string]int, 1000_000)
	for i := 0; i < 1000_000; i++ {
		m[strconv.Itoa(i)] = i
	}
	return m
}

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
// 初期サイズを与えずに map を作成する
func PraMap2() map[string]int {
	m := make(map[string]int)
	for i := 0; i < 1000_000; i++ {
		m[strconv.Itoa(i)] = i
	}
	return m
}

```


# ベンチマークテストを個別に実施
## (1)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
func BenchmarkPraMap1(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap1()
	}
	MyMap = tmpMap
}

```
```
:~/praprago$ go test -bench=. -benchmem -count=10
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap1-2             3         350494827 ns/op        65654450 B/op     999904 allocs/op
BenchmarkPraMap1-2             4         321875160 ns/op        65652784 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         321490042 ns/op        65652784 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         318642013 ns/op        65652780 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         319405101 ns/op        65652784 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         319290247 ns/op        65652784 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         315502372 ns/op        65652784 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         319453733 ns/op        65652780 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         318741874 ns/op        65652804 B/op     999903 allocs/op
BenchmarkPraMap1-2             4         322350698 ns/op        65652780 B/op     999903 allocs/op
PASS
ok      github.com/XXX/praprago 26.087s
```

## (2)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
func BenchmarkPraMap2(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap2()
	}
	MyMap = tmpMap
}

```
```
~/praprago$ go test -bench=. -benchmem -count=10
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap2-2             2         520787571 ns/op        131539664 B/op   1038072 allocs/op
BenchmarkPraMap2-2             3         455497882 ns/op        131565181 B/op   1038194 allocs/op
BenchmarkPraMap2-2             3         445089535 ns/op        131577144 B/op   1038252 allocs/op
BenchmarkPraMap2-2             3         447460783 ns/op        131563933 B/op   1038188 allocs/op
BenchmarkPraMap2-2             3         446133513 ns/op        131583202 B/op   1038281 allocs/op
BenchmarkPraMap2-2             3         448951405 ns/op        131559282 B/op   1038166 allocs/op
BenchmarkPraMap2-2             3         431831870 ns/op        131555890 B/op   1038150 allocs/op
BenchmarkPraMap2-2             3         435242665 ns/op        131527186 B/op   1038012 allocs/op
BenchmarkPraMap2-2             3         442641661 ns/op        131572674 B/op   1038230 allocs/op
BenchmarkPraMap2-2             3         448940774 ns/op        131563240 B/op   1038185 allocs/op
PASS
ok      github.com/XXX/praprago 26.770s
```

# 二つのベンチマークを同時に実施
## (1) グローバル変数を共有
### (1-1)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
func BenchmarkPraMap1(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap1()
	}
	MyMap = tmpMap
}

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
func BenchmarkPraMap2(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap2()
	}
	MyMap = tmpMap
}

```

```
~/praprago$ go test -bench=. -benchmem -count=1
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap1-2             3         342056843 ns/op        65652786 B/op     999903 allocs/op
BenchmarkPraMap2-2             3         448045973 ns/op        131547154 B/op   1038108 allocs/op
PASS
ok      github.com/XXX/praprago 4.954s
```
### (1-2)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
func BenchmarkPraMap2(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap2()
	}
	MyMap = tmpMap
}

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
func BenchmarkPraMap1(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap1()
	}
	MyMap = tmpMap
}

```
```
~/praprago$ go test -bench=. -benchmem -count=1
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap2-2             2         524669915 ns/op        131541856 B/op   1038083 allocs/op
BenchmarkPraMap1-2             4         320171822 ns/op        65652780 B/op     999903 allocs/op
PASS
ok      github.com/XXX/praprago 4.386s
```


## (2) グローバル変数を共有しない
### (2-1)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int
var MyMap2 map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
func BenchmarkPraMap1(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap1()
	}
	MyMap = tmpMap
}

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
func BenchmarkPraMap2(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap2()
	}
	MyMap2 = tmpMap
}

```
```
~/praprago$ go test -bench=. -benchmem -count=1
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap1-2             3         348708684 ns/op        65652781 B/op     999903 allocs/op
BenchmarkPraMap2-2             2         512526458 ns/op        131554440 B/op   1038143 allocs/op
PASS
ok      github.com/XXX/praprago 3.702s
```

### (2-2)
``` pra_map_test.go
package main

import "testing"

var MyMap map[string]int
var MyMap2 map[string]int

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (2)
func BenchmarkPraMap2(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap2()
	}
	MyMap2 = tmpMap
}

// /////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////// (1)
func BenchmarkPraMap1(b *testing.B) {
	var tmpMap map[string]int
	for i := 0; i < b.N; i++ {
		tmpMap = PraMap1()
	}
	MyMap = tmpMap
}

```
```
~/praprago$ go test -bench=. -benchmem -count=1
goos: linux
goarch: amd64
pkg: github.com/XXX/praprago
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
BenchmarkPraMap2-2             2         523599274 ns/op        131600312 B/op   1038364 allocs/op
BenchmarkPraMap1-2             4         323569231 ns/op        65652780 B/op     999903 allocs/op
PASS
ok      github.com/XXX/praprago 4.448s
```

# 参考

https://www.amazon.co.jp/-/en/Teiva-Harsanyi/dp/4295017531

https://github.com/kubernetes/kubernetes/issues/124656
> A hint is the extremely low time per benchmark.

https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
> **A note on compiler optimisations**
> Before concluding I wanted to highlight that to be completely accurate, any benchmark should be careful to avoid compiler optimisations eliminating the function under test and artificially lowering the run time of the benchmark.

https://tech.yyh-gl.dev/blog/go-test-cache-clear/
> 最後に$ go test ./... -count=1 によって、
> キャッシュを無効化できた理由についてです。

https://pkg.go.dev/cmd/go/internal/test
> The idiomatic way to disable test caching explicitly is to use -count=1.