Golang: String Concatenation Efficiently
Tags: go, testing, performance, programming
Published: 2020-12-25 08:41:47

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.

```go
// 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()
}
```

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

```go
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)
			}
		})
	}
}
```

_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](https://dev-to-uploads.s3.amazonaws.com/i/qruvdgb9ltl1fwbqw2xj.PNG)


|      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](https://golang.org/pkg/strings/#Builder), 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.