Go 字符串拼接性能


pprof 确认 字符串拼接 gc上耗cpu 字符串拼接的常用4个方法
加号+拼接
strings.Join()
fmt.Sprintf()
bytes.Buffer

字符串拼接性能结论

有字符串数组的 用strings.Join() 性能较好
性能要求较高的 用buffer.WriteString() 性能更好
性能要求不高   用运算符 代码简短 可读性较好
拼接的字符串   还有数字之类 用fmt.Sprintf

运算符

func BenchmarkAddStringWithOperator(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < b.N; i++ {
            _ = hello + "," + world
    }
}
golang 字符串不可变
每次运算都会产生一个新的字符串
所以产生很多临时的无用的字符串
给 gc 带来额外的负担 性能比较差

fmt.Sprintf

func BenchmarkAddStringWithSprintf(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < b.N; i++ {
            _ = fmt.Sprintf("%s,%s", hello, world)
    }
}
fmt.Sprintf内部用[]byte实现
不像运算符产生很多临时的字符串
但是内部逻辑较复杂
有很多额外的判断
还用到 interface
所以性能也不是很好

strings.Join

func BenchmarkAddStringWithJoin(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < b.N; i++ {
            _ = strings.Join([]string{hello, world}, ",")
    }
}
strings.Join先根据字符串数组的内容
计算出拼接之后的长度 后申请对应大小的内存
一个个字符串填入 在已有数组的情况下效率很高
如果没有 构造数据数组代价不小

buffer.WriteString

func BenchmarkAddStringWithBuffer(b *testing.B) {
    hello := "hello"
    world := "world"
    for i := 0; i < 1000; i++ {
            var buffer bytes.Buffer
            buffer.WriteString(hello)
            buffer.WriteString(",")
            buffer.WriteString(world)
            _ = buffer.String()
    }
}
当成可变字符使用 对内存的增长也有优化
如果能预估字符串的长度
还可以用 buffer.Grow() 接口来设置 capacity

大字符串拼接性能

大量字符串拼接 场景是不断在字符串上拼接
func BenchmarkFmtSprintfMore(b *testing.B) {// fmt.Printf
    var s string
    for i := 0; i < b.N; i++ {
     s += fmt.Sprintf("%s%s", "hello", "world")
    }
    fmt.Errorf(s)
}
func BenchmarkAddMore(b *testing.B) {// 加号 拼接
    var s string
    for i := 0; i < b.N; i++ {
    s += "hello" + "world"
    }
    fmt.Errorf(s)
}
func BenchmarkStringsJoinMore(b *testing.B) {// strings.Join
    var s string
    for i := 0; i < b.N; i++ {
      s += strings.Join([]string{"hello", "world"}, "")
    }
    fmt.Errorf(s)
}
func BenchmarkBufferMore(b *testing.B) {// bytes.Buffer
    buffer := bytes.Buffer{}
    for i := 0; i < b.N; i++ {
        buffer.WriteString("hello")
        buffer.WriteString("world")
    }
    fmt.Errorf(buffer.String())
}
测试函数
# go test -bench="."
goos: darwin
goarch: amd64
pkg: test/string
BenchmarkFmtSprintfMore-4         300000        118493 ns/op
BenchmarkAddMore-4                300000        124940 ns/op
BenchmarkStringsJoinMore-4        300000        117050 ns/op
BenchmarkBufferMore-4           100000000           37.2 ns/op
PASS
ok      test/string 112.294s
用bytes.buffer 性能非常高的,如果涉及到大量数据拼接推荐
bytes.buffer{}
单次字符串拼接性能测试
func BenchmarkFmtSprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
s := fmt.Sprintf("%s%s", "hello", "world")
fmt.Errorf(s)
}
}
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
s := "hello" + "world"
fmt.Errorf(s)
}
}
func BenchmarkStringsJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
s := strings.Join([]string{"hello", "world"}, "")
fmt.Errorf(s)
}
}
func BenchmarkBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
b := bytes.Buffer{}
b.WriteString("hello")
b.WriteString("world")
fmt.Errorf(b.String())
}}
测试go test -bench="."
goos: darwin
goarch: amd64
pkg: test/string
BenchmarkFmtSprintf-4       10000000           200 ns/op
BenchmarkAdd-4              20000000            93.6 ns/op
BenchmarkStringsJoin-4      10000000           152 ns/op
BenchmarkBuffer-4           10000000           175 ns/op
PASS
ok      test/string 7.818s
调用字符串拼接性能
+ > strings.Join > bytes.Buffer > fmt.Sprintf
+=操作符 < 准备好字符串切片([]string) 用strings.Join() 一次性将字符串串联 <
go更好的方法
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer //Buffer是个实现读写方法的可变大小的字节缓冲
for {
    if piece, ok := getNextString(); ok {
        
        buffer.WriteString(piece)
    } else {
        break
    }
}
fmt.Println("拼接后的结果为-->", buffer.String())
}
连100万字符串buffer消耗时间77毫秒
+=消耗的时间为5分钟没结果

Efficient String Concatenation in Go

//hermanschaaf.com/efficient-string-concatenation-in-go/
In summary, for very few concatenations of short strings (fewer than hundred, length shorter than 10),
using Method #1,[Method 1: Naive Appending with +=]
naive string appending, is just fine and can actually perform slightly better.
For more heavy-duty cases where efficiency is important, Method #3,
[Method 3: bytes.Buffer] bytes.Buffer is the best choice out of the methods evaluated.
That said, strings.Join is a good choice when you already have a string slice
that just needs to be concatenated into one string.