240
180

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Goの文字列結合のパフォーマンス

Posted at

Goで文字列結合をする時、通常の+=は遅いから[]byteをappendした方が高速という話があったので、実際にどの程度の差が出るのか検証してみた。

テストケース

以下のような9文字*10要素の文字列の配列要素を","で結合し、最後に","を追記するコードを実装した。得たい出力はstringなので、[]byteやbytes.Bufferを使う場合、最後にstringへのキャストを実行した。

var m = [...]string{
	"AAAAAAAAA",
	"AAAAAAAAA",
	"AAAAAAAAA",
	"AAAAAAAAA",
	"AAAAAAAAA",
	"AAAAAAAAA",
	"AAAAAAAAA",
}

使用したコードはこちら:Golang string join benchmark

実装

+=演算子のループ

ナイーブな実装。遅い。

func BenchmarkAppendOperator_(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 string
		for _, v := range m {
			m2 += m2 + "," + v
		}
		m2 += ","
	}
}
// BenchmarkAppendOperator	 1000000	      3043 ns/op	    3808 B/op	       8 allocs/op

結果:3043ns/op

strings.Join関数

strings.Joinを使った方法。

func BenchmarkStringsJoin(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = strings.Join(m[:], ",") + ","
	}
}
// BenchmarkStringsJoin	 5000000	       413 ns/op	     240 B/op	       3 allocs/op

結果:413ns/op

一度の代入文で+演算子で全要素を結合

一度の代入文で全ての要素を結合するようハードコーディングする。strings.joinよりもアロケーションが少なく高速

func BenchmarkHardCoding(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = m[0] + "," + m[1] + "," + m[2] + "," + m[3] + "," + m[4] + "," + m[5] + "," + m[6]
	}
}
// BenchmarkHardCoding	10000000	       216 ns/op	      80 B/op	       1 allocs/op

結果:216ns/op

[]byte

var m2 []byteで宣言した[]byteにappend()を繰り返す。strings.Joinよりも遅くなっている。

func BenchmarkByteArray(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 []byte
		for _, v := range m {
			m2 = append(m2, v...)
			m2 = append(m2, ',')
		}
		_ = string(m2)
	}
}
// BenchmarkByteArray______	 5000000	       613 ns/op	     320 B/op	       5 allocs/op

結果:613ns/op

キャパシティ指定付き[]byte

var m2 = make([]byte, 0, 100) で[]byteに100byteのキャパシティを確保したところ、アロケーションが少なくなり、この方法が一番高速だった。最後のstringへのキャストを除くとアロケーションは0になる。


func BenchmarkCapByteArray___(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 = make([]byte, 0, 100)
		for _, v := range m {
			m2 = append(m2, v...)
			m2 = append(m2, ',')
		}
		_ = string(m2)
	}
}
// BenchmarkCapByteArray___	10000000	       171 ns/op	      80 B/op	       1 allocs/op

結果:171ns/op

bytes.Buffer

bytes.Buffer を使ってみる

func BenchmarkBytesBuffer____(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 bytes.Buffer
		for _, v := range m {
			m2.Write([]byte(v))
			m2.Write([]byte{','})
		}
		_ = m2.String()
	}
}
// BenchmarkBytesBuffer____	 1000000	      1074 ns/op	     449 B/op	      10 allocs/op

(結果:1074 ns/op)

キャパシティ指定付き bytes.Buffer

NewBufferにキャパシティ指定した[]byteを渡してみる。

func BenchmarkCapBytesBuffer_(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 = bytes.NewBuffer(make([]byte, 0, 100))
		for _, v := range m {
			m2.Write([]byte(v))
			m2.Write([]byte{','})
		}
		_ = m2.String()
	}
}
// BenchmarkCapBytesBuffer_	 2000000	       956 ns/op	     419 B/op	      10 allocs/op

(結果:956ns/op)

キャパシティ指定付き bytes.Buffer + WriteString

Buffer.WriteのかわりにBuffer.WriteStringというメソッドを使ってみたところ、Buffer.Writeよりも速くなった。

func BenchmarkCapBytesBuffer2(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var m2 = bytes.NewBuffer(make([]byte, 0, 100))
		for _, v := range m {
			m2.WriteString(v)
			m2.WriteString(",")
		}
		_ = m2.String()
	}
}
// BenchmarkCapBytesBuffer2	 5000000	       588 ns/op	     307 B/op	       3 allocs/op

結果:588ns/op

結果一覧

BenchmarkAppendOperator_	 1000000	      3043 ns/op	    3808 B/op	       8 allocs/op
BenchmarkStringsJoin____	 5000000	       413 ns/op	     240 B/op	       3 allocs/op
BenchmarkHardCoding_____	10000000	       216 ns/op	      80 B/op	       1 allocs/op
BenchmarkByteArray______	 5000000	       613 ns/op	     320 B/op	       5 allocs/op
BenchmarkCapByteArray___	10000000	       171 ns/op	      80 B/op	       1 allocs/op
BenchmarkBytesBuffer____	 1000000	      1074 ns/op	     449 B/op	      10 allocs/op
BenchmarkCapBytesBuffer_	 2000000	       956 ns/op	     419 B/op	      10 allocs/op
BenchmarkCapBytesBuffer2	 5000000	       588 ns/op	     307 B/op	       3 allocs/op

知見

大まかに各手法での処理時間を比べると、このような感じ。

[]byte < ハードコーディング(200ns) < strings.Join(400ns) < bytes.Buffer(600ns) < +=演算子ループ(2900ns)

  • +=演算子ループはやはり一桁遅い。
    • ただし文字列の+演算子は特に遅いと言う事は無いので、s := a + b + c のように、一度の代入で複数の結合を行うのは十分に高速
  • bytes.Bufferより[]byteの方が高速
  • makeで十分なキャパシティを確保してalloc回数を下げるのが重要
  • bytes.Bufferでstringを結合する場合は、Write()よりWriteString()が高速
  • Goはこういうベンチマークがサッと書けるので良い
240
180
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
240
180

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?