In the last article on string concatenation (1) of Go language string Efficient concatenation, we demonstrated a variety of string concatenation methods, and used an example to test their performance. Through comparison, we found that the Builder with high performance did not play its supposed performance, but + sign concatenation, Even the strings.Join method performs better, so why? Today we begin to unravel their mystery, to solve the mystery.

A bonus before we get started. Ali Cloud double 11 group activity, the team has reached hundreds of people, the top 30, has begun to share millions of bonuses, hurry to join. Now join can enjoy the lowest discount, 1 year 99 yuan of cloud host, has been able to participate in the division of millions of bonuses, Ali Cloud double 11 team row 30, first invited to buy, quickly get on the bus, the old driver drive.

Modification of splicing function

At the end of the last article, I suggested two possibilities: Joining together the number and the size of the string concatenation strings, we are now starting to prove in both cases, convenient to demonstrate, we put the original stitching function change, can accept a [] the parameters of type string, so that we can to string concatenation of slice arrays, here directly given all the stitching method is modified to achieve.

func StringPlus(p []string) string{
	var s string
	l:=len(p)
	for i:=0; i<l; i++{ s+=p[i] }return s
}

func StringFmt(p []interface{}) string{
	return fmt.Sprint(p...)
}

func StringJoin(p []string) string{
	return strings.Join(p,"")}func StringBuffer(p []string) string {
	var b bytes.Buffer
	l:=len(p)
	for i:=0; i<l; i++{ b.WriteString(p[i]) }return b.String()
}

func StringBuilder(p []string) string {
	var b strings.Builder
	l:=len(p)
	for i:=0; i<l; i++{ b.WriteString(p[i]) }return b.String()
}
Copy the code

I did not use for range in the for loop in the above implementation. In order to improve performance, please refer to my performance optimization of Go language – for range performance research for specific reasons.

The test case

After the above string splicing function is modified, we can construct slices of different sizes for string splicing test. In order to simulate the last test, we first ran the concatenation test with 10 slice-sized strings, similar to the test in the previous article (also about 10 string concatenation).

const BLOG  = "http://www.flysnow.org/"

func initStrings(N int) []string{
	s:=make([]string,N)
	for i:=0; i<N; i++{ s[i]=BLOG }return s;
}

func initStringi(N int) []interface{}{
	s:=make([]interface{},N)
	for i:=0; i<N; i++{ s[i]=BLOG }return s;
}
Copy the code

These are two functions that build arrays of test force slices that can generate N size slices. The second initStringi function returns []interface{}, which is intended specifically for the StringFmt(p []interface{}) concatenation function to reduce conversions between types.

With these two test case generating functions, we are ready to build our Go language performance test, starting with 10 size slices.

func BenchmarkStringPlus10(b *testing.B) {
	p:= initStrings(10)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringPlus(p) } }func BenchmarkStringFmt10(b *testing.B) {
	p:= initStringi(10)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringFmt(p) } }func BenchmarkStringJoin10(b *testing.B) {
	p:= initStrings(10)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringJoin(p) } }func BenchmarkStringBuffer10(b *testing.B) {
	p:= initStrings(10)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringBuffer(p) } }func BenchmarkStringBuilder10(b *testing.B) {
	p:= initStrings(10)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringBuilder(p) } }Copy the code

In each performance test function, we will be called b.R esetTimer (), which is in order to avoid the test case preparation time is different, the performance test result deviation problem, specific can consult one of my article Go language field notes (22) | Go benchmarks.

We run go test-bench =. -run= none-benchmem to see the result.

BenchmarkStringPlus10-8     3000000     593 ns/op   1312 B/op   9 allocs/op
BenchmarkStringFmt10-8      5000000     335 ns/op   240 B/op    1 allocs/op
BenchmarkStringJoin10-8     10000000    200 ns/op   480 B/op    2 allocs/op
BenchmarkStringBuffer10-8   3000000     452 ns/op   864 B/op    4 allocs/op
BenchmarkStringBuilder10-8  10000000    231 ns/op   480 B/op    4 allocs/op
Copy the code

Through this we can see, splicing +. No longer have an advantage, because the string is immutable, stitching is generated every time a new string, is also a memory allocation, we are now the size of 10 slices, each operation for nine times, memory, so every operation time is longer, Natural performance is low.

Efficient String Concatenation in Go (2)

www.flysnow.org/2018/11/05/…

As you may recall from our previous article on efficient String concatenation (1) for the Go language, the performance test for + + concatenation showed only two memory allocations, but we used multiple + ones.

func StringPlus(a) string{
	var s string
	s+="Nickname"+":"+"Snow has no mercy"+"\n"
	s+="Blog"+":"+"http://www.flysnow.org/"+"\n"
	s+="Wechat Official Account"+":"+"flysnow_org"
	return s
}
Copy the code

To review this code again, yes, there are a lot of +, but only 2 memory allocations, we can take a wild guess, 3 s+= is the result of the normal 10 length of the slice we tested today, only 9 memory allocations. Go build-gcflags =”-m -m” main. Go “build-gcflags =”-m -m” main. Go

can inline StringPlus as: func() string { var s string; s = <N>; s += "Nickname: Snow Merciless \ N"; s += Blog: "http://www.flysnow.org/\n"; s += "Wechat official account: Flysnow_org"; return s }
Copy the code

Now it’s clear that the compiler has optimized the string for us, leaving only three s+=

This time, using the length of 10 slices to test, it is also obvious that the test results of Builder is much better than Buffer performance, the main reason for this problem is the conversion between []byte and String, Builder just solved this problem.

func (b *Builder) String(a) string {
	return* (*string)(unsafe.Pointer(&b.buf))
}
Copy the code

It’s a very efficient solution.

100 strings

Now let’s test the next 100 string concatenation. For our code above, it’s very easy to modify. Here’s the test code.

func BenchmarkStringPlus100(b *testing.B) {
	p:= initStrings(100)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringPlus(p) } }func BenchmarkStringFmt100(b *testing.B) {
	p:= initStringi(100)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringFmt(p) } }func BenchmarkStringJoin100(b *testing.B) {
	p:= initStrings(100)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringJoin(p) } }func BenchmarkStringBuffer100(b *testing.B) {
	p:= initStrings(100)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringBuffer(p) } }func BenchmarkStringBuilder100(b *testing.B) {
	p:= initStrings(100)
	b.ResetTimer()
	for i:=0; i<b.N; i++{ StringBuilder(p) } }Copy the code

Now run the performance test to see how well 100 string concatenations perform and which function is the most efficient.

BenchmarkStringPlus100-8 100000 19711 ns/op 123168 B/op 99 allocs/op BenchmarkStringFmt100-8 500000 2615 ns/op 2304 B/op  1 allocs/op BenchmarkStringJoin100-8 1000000 1516 ns/op 4608 B/op 2 allocs/op BenchmarkStringBuffer100-8 500000 2333 ns/op 8112 B/op 7 allocs/op BenchmarkStringBuilder100-8 1000000 1714 ns/op 6752 B/op 8 allocs/opCopy the code

The + sign is the same as the above analysis, this time it is 99 memory allocations, and the performance experience is getting worse and worse, which will be excluded in the later test.

FMT and Bufrer have not improved their performance and continue to decline. The rest are Join and Builder.

1000 strings.

The test force is much the same as in the previous section, so let’s go straight to the test results.

BenchmarkStringPlus1000-8       1000    1611985 ns/op   12136228 B/op   999 allocs/op
BenchmarkStringFmt1000-8        50000   28510 ns/op     24590 B/op      1 allocs/op
BenchmarkStringJoin1000-8       100000  15050 ns/op     49152 B/op      2 allocs/op
BenchmarkStringBuffer1000-8     100000  23534 ns/op     122544 B/op     11 allocs/op
BenchmarkStringBuilder1000-8    100000  17996 ns/op     96224 B/op      16 allocs/op
Copy the code

The total is about the same as 100 strings, with joins and Builders doing better. The two methods have different emphases. If you have existing arrays and slices, you can use Join directly, but if you don’t have them and want to Join them flexibly, you still choose Builder. Join is still targeted at ready-made slices and arrays (after all, it takes time to Join into arrays), and is decomposed in fixed ways, such as commas and Spaces, which are relatively limited.

summary

I’m not going to test the 10000 string concatenation here, but you can try it out and see if it’s pretty much the same.

From the analysis of these two recent articles, we can probably conclude that.

  1. +Concatenation works for short, constant strings (explicit, invariant) because the compiler optimizes for us.
  2. JoinIs more unified splicing, not too flexible
  3. fmtandbufferBasically not recommended
  4. builderIt is a superior choice for performance and flexibility.

Is that it? This article is over, and IT’s time for me to go to bed. But efficient string concatenation is not the end of the story. This is not the end of the story.

This article is an original article, reprinted with notes of origin, “there are always bad people grab the article when also remove my original description” welcome to scan the code to follow the public number flysnow_org or www.flysnow.org/, the first time to see the follow-up wonderful article. “Anti-rotten person remarks **…… &*¥” feel good, feel free to share it in moments, thank you for your support.