DEV Community

Jonathan Law
Jonathan Law

Posted on • Updated on

Golang: String Concatenation Efficiently

Hi everyone!

I am creating a series: Random Golang Stuff that people may be interested about. This idea came about when I was trying out Golang, and I realized many people who were just starting out had the same question as me, how to concat in golang efficiently? Which would be faster? Why is it performing slower when it was supposed to be the fastest? Both function does the same thing, which should I use?

Hopefully with this series, everyone would have a brief idea of the topic to be discussed without going too technical! Without further ado, let's jump right into the first part of the series!

String Concatenation

Everyone knows about adding 2 strings together. The very basic stringA = stringB + stringC applies to nearly all the programming languages. However as we move deeper into it, you will realize there are more ways to join a string, whether it is using StringBuilder, or putting a list of string in an array and .join() them.

Today, we will be running some benchmarks using Golang as the programming language to clear some doubts. The three methods that will be used are the commonly found methods on various forums and guides, the strings().join() method, the + method and the StringBuilder method.

Setup

At the time of posting this, I am currently using go1.15.6 windows/amd64 on a Windows 10 machine with i7 3770K and 16GB RAM.

main.go was setup to contains 3 string concat function with different methods. Each function receives a string to concat to (concatMe), and the number of times (iterations) it concats to itself.

// Concat by joining an array of strings
func arrayConcat(concatMe string, iterations int) string {
    var concatenatedString string

    for i := 0; i < iterations; i++ {
        concatenatedString = strings.Join([]string{ concatenatedString, concatMe }, "")
    }

    return concatenatedString
}

// Concat by adding the string using +
func additionConcat(concatMe string, iterations int) string {
    var concatenatedString string

    for i := 0; i < iterations; i++ {
        concatenatedString += concatMe
    }

    return concatenatedString
}

// Concat using StringBuilder
func sbConcat(concatMe string, iterations int) string {
    var sb strings.Builder

    for i := 0; i < iterations; i++ {
        sb.WriteString(concatMe)
    }

    return sb.String()
}
Enter fullscreen mode Exit fullscreen mode

main_test.go utilizing Golang built in testing package was setup for benchmarking too.

var TestingIterationsSeq = [5]int{1, 10, 100, 1000, 10000}
var TestingString = "abcdefgh"

func BenchmarkArrayConcat(b *testing.B) {
    for _, seq := range TestingIterationsSeq {
        b.Run(fmt.Sprintf("seq-%d", seq), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                arrayConcat(TestingString, seq)
            }
        })
    }
}

func BenchmarkAddConcat(b *testing.B) {
    for _, seq := range TestingIterationsSeq {
        b.Run(fmt.Sprintf("seq-%d", seq), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                additionConcat(TestingString, seq)
            }
        })
    }
}

func BenchmarkSBConcat(b *testing.B) {
    for _, seq := range TestingIterationsSeq {
        b.Run(fmt.Sprintf("seq-%d", seq), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                sbConcat(TestingString, seq)
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

TestingIterationsSeq will test each condition, where we concatenate TestingString 1, 10, 100, 1000 and 10000 times. To clear things up, one time would produce a result of abcdefghabcdefgh.

Testing

Running go test -bench=. -benchmem (each test would run for 1 second by default) would produce a result similar to below
Golang String Concat Results

Concat Type TestingIterationsSeq Loops ns/op b/op allocs/op
BenchmarkArrayConcat 1 22235810 50.5 8 1
10 1705158 657 472 10
100 71788 15111 42232 100
1000 1222 959981 4273940 1000
10000 14 81298429 428520585 10036
Concat Type TestingIterationsSeq Loops ns/op b/op allocs/op
BenchmarkAddConcat 1 70603600 17.3 0 0
10 1786455 682 464 9
100 81554 14626 42224 99
1000 1276 920909 4273921 999
10000 18 66758850 428521876 10052
Concat Type TestingIterationsSeq Loops ns/op b/op allocs/op
BenchmarkSBConcat 1 30897654 37.6 8 1
10 4274847 297 248 5
100 923659 1362 2040 8
1000 90282 13307 36216 16
10000 8575 129611 382970 24

The number of loops indicates how many times the string concat function could be called in a second. By looking at the summarized table above, StringBuilder is significantly faster and uses the least memory when concatenating large number of strings. The reason being it is optimized to reduce memory copying reference, whereby + and array join methods generate a new string (see allocs/op).

Now with that being said, does it means I should use StringBuilder always? It really depends on the use case of your program. Looking back at the table, concatenating 2 string once using + yields better result than using StringBuilder. Coming to 10-100 strings may still be considered negligible.

So how do I concat strings in Golang efficiently

Now that you have read until this paragraph, you deserve a summary! In most cases of writing small Golang applications, the + method does fine, almost unnoticeable. Going for larger applications that scales, you might want to benchmark your codes before assuming StringBuilder solves everything.

Thank you if you have made it this far!! Please leave a feedback on how I could do better (article format, methodology etc) as this is my first time attempting to create series like this. I would be comparing the differences (performance, just like this chapter) between passing a value and passing a pointer reference to a function parameter in the next part of the series.

Oldest comments (0)