Goで並行処理のベンチマークをとる

## 概要
Goの高速なHTTPサーバ、クライアントパッケージである[fasthttp](https://github.com/valyala/fasthttp)について調査していました。ザーッとfasthttpのコードを追っていて、ベンチマークの処理が気になったので[中身を見てみると](https://github.com/valyala/fasthttp/blob/0d8bc2c01a6df2c8c6f14194c5237c51358ccd1e/client_timing_test.go#L251-L303)、タイトルのような目的でベンチマークを取っていました。
あまり日本語の情報がなかったので、メモを残しておきます。

ちなみにGoのバージョンは1.6です。

## コード例
実際にfasthttpのベンチマークのコードを見るとわかるかもしれませんが、
簡単な例を残しておきます。

```go:http_test.go
package hoge

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"testing"
)

func benchmarkWithParallel(b *testing.B, parallelism int) {
	addr := "127.0.0.1:9999"

	ln, err := net.Listen("tcp4", addr)
	if err != nil {
		b.Fatalf("cannot listen %q: %s", addr, err)
	}

	handler := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "hello, world")
	}

	ch := make(chan struct{})
	go func() {
		if err := http.Serve(ln, http.HandlerFunc(handler)); err != nil && !strings.Contains(
			err.Error(), "use of closed network connection") {
			b.Fatalf("http.Serve() error: %s", err)
		}
		close(ch)
	}()

	c := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: parallelism,
		},
	}

	URL := "http://" + addr + "/hello"
	b.SetParallelism(parallelism)
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			resp, err := c.Get(URL)
			if err != nil {
				b.Fatalf("http.Get() error: %v", err)
			}
			_, _ = ioutil.ReadAll(resp.Body)
			resp.Body.Close()
		}
	})

	ln.Close()
	<-ch
}

func BenchmarkNilContinueWithParallel1(b *testing.B) {
	benchmarkWithParallel(b, 1)
}

func BenchmarkNilContinueWithParallel2(b *testing.B) {
	benchmarkWithParallel(b, 2)
}

func BenchmarkNilContinueWithParallel10(b *testing.B) {
	benchmarkWithParallel(b, 10)
}

func BenchmarkNilContinueWithParallel20(b *testing.B) {
	benchmarkWithParallel(b, 20)
}
```

実行結果

```shell-session
$ go test -bench Parallel -benchmem -cpu=1,2,4 -benchtime=2s
testing: warning: no tests to run
PASS
BenchmarkNilContinueWithParallel1   	   50000	     65503 ns/op	    4236 B/op	      49 allocs/op
BenchmarkNilContinueWithParallel1-2 	   30000	    152424 ns/op	    5587 B/op	      54 allocs/op
BenchmarkNilContinueWithParallel1-4 	    5000	    844317 ns/op	    9953 B/op	      70 allocs/op
BenchmarkNilContinueWithParallel2   	   50000	     53632 ns/op	    4236 B/op	      49 allocs/op
BenchmarkNilContinueWithParallel2-2 	   50000	     48256 ns/op	    4260 B/op	      49 allocs/op
BenchmarkNilContinueWithParallel2-4 	   10000	    564127 ns/op	    6446 B/op	      57 allocs/op
BenchmarkNilContinueWithParallel10  	   50000	     58109 ns/op	    4231 B/op	      48 allocs/op
BenchmarkNilContinueWithParallel10-2	   50000	     47181 ns/op	    4242 B/op	      48 allocs/op
BenchmarkNilContinueWithParallel10-4	  100000	     34683 ns/op	    4247 B/op	      48 allocs/op
BenchmarkNilContinueWithParallel20  	    3000	    943406 ns/op	    4364 B/op	      48 allocs/op
BenchmarkNilContinueWithParallel20-2	   50000	     60116 ns/op	    4243 B/op	      48 allocs/op
BenchmarkNilContinueWithParallel20-4	  100000	     33019 ns/op	    4245 B/op	      48 allocs/op
ok  	_/tmp_golang/bbb	49.072s
```


## RunParallelとSetParallelism
[パッケージドキュメント](https://golang.org/pkg/testing/#B.RunParallel)に`RunParallel`の動作について説明があります。

> RunParallelは並行処理のベンチマークを実施する。複数のgoroutinesを作成し、b.Nの中で分散実行する。
> goroutinesの数はデフォルトでGOMAXPROCSになっている。CPUバウンドではないベンチマークの場合、
> RunParallelを呼ぶ前にSetParallelismを呼んで並行数を増やす。
>
> body関数は1goroutine毎に実行される。goroutineローカルな処理を書き、pb.Nextがfalseを返すまで実行するべき。
> StartTimer、StopTimer、ResetTimer関数を内部で使うべきではない。なぜなら、それらはグローバルな影響を与えるため。

さらに[SetParallelismの項](https://golang.org/pkg/testing/#B.SetParallelism)にも説明があります。

> SetParallelism(p int)はRunParallelで使うgoroutinesの数をp*GOMAXPROCSに設定する。
> CPUバウンドなベンチマークでは通常SetParallelismを呼ぶ必要はない。
> もしpが1未満の場合、この呼び出しはなにも効果がない。

## その他
知ってる人は知ってそうなTipsっぽいですが、知らなかったのでメモ。

* テストの中で何か出力して確認したい場合は`testing.T.Log`とか`testing.B.Log`を使う。
* テスト用にHTTPサーバを使いたい場合は`net/http/httptest`の`httptest.NewServer`というまさにそれなのがすでにある。

いじょ