Slide 1

Slide 1 text

strings.Builder の紹介 Golang 1.10 release party 鎌田 健史

Slide 2

Slide 2 text

自己紹介 ● 鎌田 健史 ● @knsh14 (twitter, GitHub) ● KLab 株式会社 ○ Unity のエディタ拡張書いたり ○ JavaScript でゲーム書いたり ● 技術書典ではバイトしてました

Slide 3

Slide 3 text

strings.Builder って何 ? ● https://golang.org/pkg/strings/#Builder ● 1.10 から入った新しいデータ型 ● 文字列を構築するために使われる

Slide 4

Slide 4 text

strings.Builder の使い方 https://github.com/knsh14/monkey/blob/master/ast/ast.go#L182-L1 96 こういう箇所を string.Builder で置き換えたい

Slide 5

Slide 5 text

bytes.Buffer との比較 bytes.Buffer strings.Builder io.Writer を実装している ◯ ◯ io.Reader を実装している ◯ ✕ 初期値を与える NewXXX(s string) ◯ ✕

Slide 6

Slide 6 text

strings.Builder を作った理由 ● Issue ○ https://github.com/golang/go/issues/18990 ○ https://go-review.googlesource.com/c/go/+/74931 ● bytes.Buffer に溜め込んだ文字列を string にする時に発生する余 計なアロケーションを回避したい

Slide 7

Slide 7 text

パフォーマンス見てみる https://gist.github.com/knsh14/fd4b0413a32b8b2ca544f2706d8557 d0 1. 100 回 WriteString してその結果を n 回取り出す 2. n 回 WriteString してその結果を String() で取り出す 3. 事前に n * 10 で容量を確保しておき、2と同じことをする 4. これを 10, 100, 1000, 10000, 100000 回で試す

Slide 8

Slide 8 text

1. については確かに早くなっている ● 回数が多くなればなるほど差が開いていく ● 10万回 String() すると 400倍近い差がでる ● Allocation は 100000 回呼ぶと 約 1/12500

Slide 9

Slide 9 text

なぜだろう? ● bytes.Buffer の String → メモリコピーが走る ○ String(b []byte)している ● strings.Builder の String → メモリコピーが走らない ○ unsafe.Pointer からのキャスト ● ちょうど修正したところが活かされている

Slide 10

Slide 10 text

もう一つのベンチマーク ● n 回 WriteString して書き込んで一度だけ String() を読んで文字列 を取得すると若干遅くなる ● 特に allocation とメモリのパフォーマンスが落ちる ● 事前に Grow(n int) している場合には遅くならない ○ Grow は Buffer の cap を増やす処理 ● 実装を調べてみる

Slide 11

Slide 11 text

bytes.Buffer の WriteString ● https://golang.org/src/bytes/buffer.go?s=6183:6240#L172 ● cap にまだ書き込める余裕があるかチェック ● 余裕がなければ make で cap を増やす ● 増えたら copy で書き込む

Slide 12

Slide 12 text

strings.Builder の WriteString ● https://tip.golang.org/src/strings/builder.go?s=3201:3253#L105 ● 自分自身がコピーされたものじゃないかチェック ● append する

Slide 13

Slide 13 text

実装の比較 ● bytes.Buffer の grow は cap を増やす時に もとの長さ * 2 + 増や す分 の長さを取っている ○ https://golang.org/src/bytes/buffer.go?s=4258:4292#L144 ● strings.Builder は append(d []byte, s string) ● 繰り返しが小さいと差が出づらい ○ 小さい要素だと append も倍増やす仕組みになっているらしい ● この差で bytes.Buffer は少し make する回数が減っているのでは ないか?

Slide 14

Slide 14 text

どう使い分けるのがいいのか? ● どれくらい buffer に書き込むか事前にわからない場合は bytes.Buffer のほうが少し有利 ● String() を何回も呼ぶ必要がある場合には strings.Builder のほう が圧倒的に有利 ● 事前にどれくらい書き込むかわかっている場合には strings.Builder がおすすめ

Slide 15

Slide 15 text

更に細かい実装の話

Slide 16

Slide 16 text

コピーされたかどうか検知する ● https://play.golang.org/p/nzYsBpxYZpc ● struct をコピーすると、フィールドがポインタの場合はアドレスがコ ピーされる ○ フィールドのアドレスが共有され、思わぬ副作用が ● これを回避したい ○ 最初に書き込むときに自分自身のアドレスを持っておく ○ B := A とコピーする ○ B 自身のポインタとフィールドが持っているポインタが違うのでコ ピーされたことが検出できる

Slide 17

Slide 17 text

noescape を使ってパフォーマンス改善 ● https://github.com/golang/go/issues/23382 ● https://go-review.googlesource.com/c/go/+/86976 ● コピーされてないかチェックするだけでメモリ確保が走ってしまうのを 防ぐ ● `//go:nosplit` がついてる関数ではスタックの操作はしない ○ Goならわかるシステムプログラミング Go言語のメモリ管理 を 参考にしました