strings.Joinの中身を見てみる
strings.Joinの中身を見てみる
// Join concatenates the elements of its first argument to create a single string. The separator // string sep is placed between elements in the resulting string. func Join(elems []string, sep string) string { switch len(elems) { case 0: return "" case 1: return elems[0] } n := len(sep) * (len(elems) - 1) for i := 0; i < len(elems); i++ { n += len(elems[i]) } var b Builder b.Grow(n) b.WriteString(elems[0]) for _, s := range elems[1:] { b.WriteString(sep) b.WriteString(s) } return b.String() }
早期リターン
最初に、Strings.Builferによる結合が必要がないものについてはそのまま要素を返す
switch len(elems) { case 0: return "" case 1: return elems[0] }
確保するメモリを前もって計算
Joinの第二引数であるセパレーターの要素数と、結合対象となる文字列スライスの要素数を掛け算し、必要なセパレーターの数を計算 次に文字列スライスの要素数分のだけnに加算していく。
n := len(sep) * (len(elems) - 1) for i := 0; i < len(elems); i++ { n += len(elems[i]) }
メモリの確保
var b Builder
b.Grow(n)
b.Glow(n)で先程計算したメモリ量分だけメモリ領域を拡張する。
// Grow grows b's capacity, if necessary, to guarantee space for // another n bytes. After Grow(n), at least n bytes can be written to b // without another allocation. If n is negative, Grow panics. func (b *Builder) Grow(n int) { b.copyCheck() if n < 0 { panic("strings.Builder.Grow: negative count") } if cap(b.buf)-len(b.buf) < n { b.grow(n) } }
コメント部分和訳
Grow は、必要に応じて b の容量を増やし、さらに n バイト分のスペースを保証します。Grow(n) の後、少なくとも n バイトを b に書き込むことができます。n が負の値の場合、Grow はパニックに陥ります。
メモリ領域が確保されたbuilderに対してappend
結合対象となるスライスとセパレーターの数だけメモリ領域が確保されたbuilderに対し、WriteStringを行って結合処理をする。
b.WriteString(elems[0]) for _, s := range elems[1:] { b.WriteString(sep) b.WriteString(s) } return b.String()
おまけ
strings.Join内のb.Glow(n)を外したらどうなるのか気になったので実験した。
package main import ( "fmt" "strconv" "strings" "testing" ) var slice []string func Benchmark(b *testing.B) { slice = gen(b, 1000) b.ResetTimer() BenchmarkJoin(b) } func BenchmarkJoin(b *testing.B) { fmt.Println(strings.Join(slice, " a ")) } func gen(b *testing.B, n int) []string { var slice []string for i := 0; i < n; i++ { slice = append(slice, strconv.Itoa(i)) } return slice }
b.Glow(n)を使わないようにコメントアウト
var b Builder // b.Grow(n) b.WriteString(elems[0])
結合処理全体の処理時間におけるベンチマークが以下。 実験時の条件だけ踏まえれば、b.Glow(n)の有無によるベンチマーク差がそのままappend()処理時にスライスの拡張が必要になった場合のメモリアロケーションによるベンチマーク差と一致する。
b.Glow(n)あり | b.Glow(n)なし | 差 | |
---|---|---|---|
n=100 | 0.000017 ns/op | 0.000024 ns/op | 0.000007 ns/op |
n=1000 | 0.000044 ns/op | 0.000064 ns/op | 0.000020 ns/op |
n=10000 | 0.000221 ns/op | 0.000586 ns/op | 0.000365 ns/op |
n=100000 | 0.008440 ns/op | 0.010600 ns/op | 0.002160 ns/op |
n=1000000 | 0.129000 ns/op | 0.135000 ns/op | 0.006000 ns/op |