dtruss(dtrace)を使って、みんなのGo言語に載っているコードを試してみる

現在大きめの会社で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

## 参考
- https://qiita.com/kaz3284/items/21bc43ba703b66795a87
- https://www.amazon.co.jp/dp/B01LMS7B1O