I was writing some Go code today that generated other Go code. Writing it line by line, mostly in a loop, but with pre- and post-matter.
My usual go to for this type of thing is bytes.Buffer
, but after I’d finished the implementation, given that I was working entirely with strings, I started to wonder if I should’ve used strings.Builder
instead.
I realized that I had no idea whether one was faster than the other, so I wrote a quick benchmark to check:
package main
import (
"bytes"
"strings"
"testing"
)
var fragments = []string{
"This",
"is a series of",
"string fragments",
"that will be concatenated together",
"into a single larger string",
"so that we can",
"determine which of Go's various",
"tools for doing this",
"is most efficient.",
"I found a few articles",
"online",
"but most were poorly cited",
"or",
"behind a Medium login wall",
"or otherwise",
"not of admirable quality.",
}
func BenchmarkBytesBuffer(b *testing.B) {
for range b.N {
var buf bytes.Buffer
for _, fragment := range fragments {
buf.WriteString(fragment)
buf.WriteString(" ")
}
_ = buf.String()
}
}
func BenchmarkConcatenateStrings(b *testing.B) {
for range b.N {
var str string
for _, fragment := range fragments {
str += fragment
str += " "
}
}
}
func BenchmarkStringBuilder(b *testing.B) {
for range b.N {
var sb strings.Builder
for _, fragment := range fragments {
sb.WriteString(fragment)
sb.WriteString(" ")
}
_ = sb.String()
}
}
$ go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/brandur/go-builder-vs-buffer
cpu: Apple M4
BenchmarkBytesBuffer-10 5013081 217.3 ns/op 1280 B/op 5 allocs/op
BenchmarkConcatenateStrings-10 1603748 753.5 ns/op 5557 B/op 31 allocs/op
BenchmarkStringBuilder-10 6916813 146.9 ns/op 752 B/op 6 allocs/op
PASS
ok github.com/brandur/go-builder-vs-buffer 4.724s
So there you have it. At least when it comes to concatenating only strings at relatively modest sizes, strings.Builder
is about 33% faster, and 80% faster than 1 than concatenating strings. Given that the DX is identical between the two, I’ll make it my new default go to.
1 Put otherwise, bytes.Buffer
is 50% slower than strings.Builder
and concatenating strings is 500% slower.
Did I make a mistake? Please consider sending a pull request.