bytes.Buffer for string concatenation in Go
The differences between methods of string concatenation in Go.
July 13, 2015
Just thought I would write a quick reminder on how to properly do string concatenation in Go.
Every now and again I see string concatenation done like this: myString := myOtherString + anotherString
. This is not the most effective way.
Let me show you an example. Recently I quickly wrote a library to slugify titles for one of my personal projects. When hacking it together I used string concatenation like demonstrated earlier.
slugify-slow.go
// Slugify creates a slug for a string
func (s Slugifier) Slugify(value string) string {
value = strings.ToLower(value)
res := ""
lastCharacterWasInvalid := false
for i := 0; i < len(value); i++ {
c := value[i]
if s.isValidCharacter(c) {
res = res + string(c)
lastCharacterWasInvalid = false
} else if lastCharacterWasInvalid == false {
res = res + string(s.replaceCharacter)
lastCharacterWasInvalid = true
}
}
return strings.Trim(res, string(s.replaceCharacter))
}
All swell, now see the benchmark for this piece of code:
slugify-slow.go benchmark
$ go test -bench=.
PASS
BenchmarkSlugify 500000 2794 ns/op
BenchmarkSlugifyLongString 2000 552774 ns/op
ok github.com/Machiel/slugify 2.609s
When I saw this, I was not satisfied, and I knew I could improve it by using bytes.Buffer
instead.
This is how you would write myString := myOtherString + anotherString
using bytes.Buffer
.
var buffer bytes.Buffer
buffer.WriteString(myOtherString)
buffer.WriteString(anotherString)
myString := buffer.String()
Easy, huh? Now let’s take a look at the changed code and and the drastically improved results:
slugify-less-slow.go
// Slugify creates a slug for a string
func (s Slugifier) Slugify(value string) string {
value = strings.ToLower(value)
var buffer bytes.Buffer
lastCharacterWasInvalid := false
for i := 0; i < len(value); i++ {
c := value[i]
if s.isValidCharacter(c) {
buffer.WriteByte(c)
lastCharacterWasInvalid = false
} else if lastCharacterWasInvalid == false {
buffer.WriteByte(s.replaceCharacter)
lastCharacterWasInvalid = true
}
}
return strings.Trim(buffer.String(), string(s.replaceCharacter))
}
slugify-less-slow.go benchmark
$ go test -bench=.
PASS
BenchmarkSlugify 1000000 1269 ns/op
BenchmarkSlugifyLongString 30000 42735 ns/op
ok github.com/Machiel/slugify 2.979s
As you can see, there is around a 100% performance gain for the BenchmarkSlugify
and around a 50% performance gain for the BenchmarkSlugifyLongString
.
So that’s it, use bytes.Buffer
:).
Quick tip, for simple concatenations, you could use a function like this:
func concat(values ...string) string {
var buffer bytes.Buffer
for _, s := range values {
buffer.WriteString(s)
}
return buffer.String()
}
Or, alternatively, you could write this: strings.Join([]string{"Hi", ",", " ", "World!"}, "")
. Both are faster than myString := "Hi" + "," + " " + "World!"
! I simply prefer the concat
function, because I find it more readable.