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.
尊贵的董事大人
英文标题不为空时 视为本栏投稿
需要关键字 描述 英文标题