bytes.Buffer revisited
Always be benchmarking
Two years ago I wrote a post about building up strings with bytes.Buffer. I wondered what’s changed over the past two years?
Here are the benchmarks taken from the original post.
BenchmarkCopyKey-8 114 ns/op 31 B/op 1 allocs/op
BenchmarkSimpleKey-8 141 ns/op 31 B/op 1 allocs/op
BenchmarkSimpleMultilineKey-8 256 ns/op 63 B/op 4 allocs/op
BenchmarkSprintfKey-8 392 ns/op 79 B/op 4 allocs/op
BenchmarkJoinKey-8 156 ns/op 63 B/op 2 allocs/op
BenchmarkBufferKey-8 268 ns/op 175 B/op 3 allocs/op
BenchmarkSimpleBufferKey-8 265 ns/op 143 B/op 2 allocs/op
If we run the same benchmarks with the latest version of Go — 1.11
BenchmarkCopyKey-8 64.4 ns/op 31 B/op 1 allocs/op
BenchmarkSimpleKey-8 85.3 ns/op 31 B/op 1 allocs/op
BenchmarkSimpleMultilineKey-8 190 ns/op 63 B/op 4 allocs/op
BenchmarkSprintfKey-8 280 ns/op 79 B/op 4 allocs/op
BenchmarkJoinKey-8 94.8 ns/op 31 B/op 1 allocs/op
BenchmarkBufferKey-8 194 ns/op 175 B/op 3 allocs/op
BenchmarkSimpleBufferKey-8 164 ns/op 143 B/op 2 allocs/op
Every option is considerably faster than before! The strings.Join
version has one less allocation. Presumably the compiler has improved and one of the allocations has been optimised away or is now kept on the stack.
There is now a new option for building strings — strings.Builder
. This appears to be aimed precisely at this use-case. We can build our key as follows.
w := strings.Builder{}
w.Grow(len(itemType) + len(clientId) + len(id) + 2)
w.WriteString(itemType)
w.WriteRune(':')
w.WriteString(clientId)
w.WriteRune(':')
w.WriteString(id)
key := w.String()
This turns out to be as fast as our fastest option.
BenchmarkStringBuilderKey-8 69.6 ns/op 31 B/op 1 allocs/op
So, bytes.Buffer
still isn’t your friend for this use-case. Just use + in simple cases, and reach for strings.Builder
in more complex cases.