概要
Goの高速なHTTPサーバ、クライアントパッケージであるfasthttpについて調査していました。ザーッとfasthttpのコードを追っていて、ベンチマークの処理が気になったので中身を見てみると、タイトルのような目的でベンチマークを取っていました。
あまり日本語の情報がなかったので、メモを残しておきます。
ちなみにGoのバージョンは1.6です。
コード例
実際にfasthttpのベンチマークのコードを見るとわかるかもしれませんが、
簡単な例を残しておきます。
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)
}
実行結果
$ 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
パッケージドキュメントにRunParallel
の動作について説明があります。
RunParallelは並行処理のベンチマークを実施する。複数のgoroutinesを作成し、b.Nの中で分散実行する。
goroutinesの数はデフォルトでGOMAXPROCSになっている。CPUバウンドではないベンチマークの場合、
RunParallelを呼ぶ前にSetParallelismを呼んで並行数を増やす。body関数は1goroutine毎に実行される。goroutineローカルな処理を書き、pb.Nextがfalseを返すまで実行するべき。
StartTimer、StopTimer、ResetTimer関数を内部で使うべきではない。なぜなら、それらはグローバルな影響を与えるため。
さらに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
というまさにそれなのがすでにある。
いじょ