背景
最近在做一個服務發現/註冊的agent, 各個服務需要通過這個agent來註冊自己的服務,在完成
開發後,測試性能時發現性能達不到要求,通過pprof 來確認cpu主要耗費在gc上,分析結果主要是由於字符串拼接導致,故需要測試一下字符串拼接的幾種方法的性能;
字符串拼接的幾種方法
- 直接使用加號進行拼接
- strings.Join()
- fmt.Sprintf()
- bytes.Buffer
大量字符串拼接性能測試
我們使用的場景主要是大量字符串拼接,所以需要的場景是不斷在字符串上拼接所以測試函數如下:
// fmt.Printf
func BenchmarkFmtSprintfMore(b *testing.B) {
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)
}
// strings.Join
func BenchmarkStringsJoinMore(b *testing.B) {
var s string
for i := 0; i < b.N; i++ {
s += strings.Join([]string{"hello", "world"}, "")
}
fmt.Errorf(s)
}
// bytes.Buffer
func BenchmarkBufferMore(b *testing.B) {
buffer := bytes.Buffer{}
for i := 0; i < b.N; i++ {
buffer.WriteString("hello")
buffer.WriteString("world")
}
fmt.Errorf(buffer.String())
}
執行測試函數
~/gopath/src/test/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())
}
}
執行測試函數
~/gopath/src/test/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
總結
如果涉及到大量數據拼接推薦 bytes.buffer{}
後記
- 當然只是通過bytes.buffer{} 性能還是不夠的,針對這個問題我們通過添加緩存進一步接口qps.
- cpu 耗費在gc 上的原因,需要分析golang gc 回收機制, 這是另外一個topic, 之後總結。