Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go1.10 strings.Builder の紹介

Kenshi Kamata
February 20, 2018

Go1.10 strings.Builder の紹介

Go 1.10 Release Party in Tokyo の発表です

Kenshi Kamata

February 20, 2018
Tweet

More Decks by Kenshi Kamata

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. パフォーマンス見てみる
    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 回で試す

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 実装の比較
    ● 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 する回数が減っているのでは
    ないか?

    View full-size slide

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

    View full-size slide

  15. 更に細かい実装の話

    View full-size slide

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

    View full-size slide

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

    View full-size slide