現在大きめの会社でSREチームとして働いています。
範囲はモバイルアプリから、インフラまで色々やります。
今回はSRE関連でパフォーマンスチューニングのテスト自動化ツールを作っているときに、ファイルにjsonを書き出す必要があったため、そこでのパフォーマンスについて、「みんなのGo言語」という本の3.3章で触れていたので、それをdtrussで試してみました。
環境
Mac 10.12 Sierra
※dtrussはバージョンにもよりますがデフォルトで入っています
code
https://github.com/revenue-hack/go-sample
「みんなのGo言語」(以下みんGo)の3.3の効率的なI/O処理という部分の話
説明
今回はGoでバッファリングすることでwriteのシステムコールが減らせたことを見るので、dtruss
の詳しい説明は省きます。とりあえずコマンドのシステムコールをtraceするものと思っておけば大丈夫です。
まずGoでは自動的にバッファリングは行われません。
LL系だと場合によって自動でバッファリングが行われるのですが、Goはそのような仕組みはないので意図的にバッファリングをする必要があります。
早速みんGoで書かれているコードをそのまま使ってdtraceでシステムコールを確認していきます。
バッファリングしない場合
func noBuffer() {
// wirteを100回call
for i := 0; i < 100; i++ {
fmt.Fprintln(os.Stdout, strings.Repeat("x", 100))
}
}
ただ「xxxx......」を表示しているだけです。
実際にこのコードを実行した際のtraceを見てみます。
go build buffer_bench.go
sudo dtruss -c ./buffer_bench
結果
CALL COUNT
bsdthread_register 1
close 1
exit 1
open 1
read 1
stat64 2
sysctl 3
bsdthread_create 4
mmap 9
sigaltstack 10
__pthread_sigmask 14
sigaction 50
select 58
write 100
write 100
システムコールのwriteが100回行われたことを意味します。
バッファリングした場合
func buffer() {
// bufferによってwriteのcallを少なくできる
buf := bufio.NewWriter(os.Stdout)
for i := 0; i < 100; i ++ {
fmt.Fprintln(buf, strings.Repeat("x", 100))
}
buf.Flush()
}
続いてみんごGoに書かれている通り、バッファリングした場合は上記のようなコードになります。
NewWriter
にはio.Reader
を引数として渡すので、ここにファイルポインタなり、os.Stdoutなりを渡すようにします。
また最後にbuf.Flush()
するのはbufferの残を全て吐き出すためです。
では以下がdtraceの結果です。
CALL COUNT
bsdthread_register 1
close 1
exit 1
open 1
read 1
stat64 2
sysctl 3
write 3
bsdthread_create 4
mmap 8
sigaltstack 10
__pthread_sigmask 14
select 32
sigaction 50
write 3
するとwriteは3回になっています。
挙動としてはbufferを使ってない場合と同じなのですが、バッファリングしたことで、バッファが溜まったときにwriteのシステムコールがcallされるので、writeのコールは格段と減り、パフォーマンスの向上に繋がります。
以上となります。
バッファリングはパフォーマンスでは欠かせないので、気をつけていきましょう。
またこちら、参考になればと思います。
https://github.com/revenue-hack/go-sample