开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情
七种拼接方式
+ 加号拼接
go语言字符串类型是不可编辑的,加号直接拼接相当于创建一个新的字符串,赋值为拼接后的值。以拼接数字1到n为例:
func BenchmarkStringAdd(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var s string
for i := 0; i < numbers; i++ {
s += strconv.Itoa(i)
}
}
b.StopTimer()
}
fmt.Sprintf
fmt.Sprintf是fmt包提供的字符串生成函数,可以将约定格式的字符串直接返回。
func BenchmarkSprintf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var s string
for i := 0; i < numbers; i++ {
s = fmt.Sprintf("%v%v", s, i)
}
}
b.StopTimer()
}
string Buffer
string Buffer底层用byte切片存储字符串,可以动态扩展
func BenchmarkBytesBuf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var buf bytes.Buffer
for i := 0; i < numbers; i++ {
buf.WriteString(strconv.Itoa(i))
}
_ = buf.String()
}
b.StopTimer()
}
string Builder
string Builder与string Buffer类型,但是toString方法可以原地返回字符串,无需开辟新空间。
func BenchmarkStringBuilder(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var builder strings.Builder
for i := 0; i < numbers; i++ {
builder.WriteString(strconv.Itoa(i))
}
_ = builder.String()
}
b.StopTimer()
}
[]byte
byte切片可以append进行字符拼接,最后返回string即可。
func BenchmarkBytes(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
bytes := make([]byte, 0)
for i := 0; i < numbers; i++ {
bytes = append(bytes, strconv.Itoa(i)...)
}
_ = string(bytes)
}
b.StopTimer()
}
[]rune
rune切片与byte切片类似,不过rune占3个字节相当于一个uint,1个rune可以存储一个汉字。
func BenchmarkRunes(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
runes := make([]rune, 0)
for i := 0; i < numbers; i++ {
runes = append(runes, []rune(strconv.Itoa(i))...)
}
_ = string(runes)
}
b.StopTimer()
}
预分配空间的切片
append主要的开销是切片长度不够,重新分配空间。别小看这点常数级优化。
func BenchmarkStringBytes2(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
bytes := make([]byte, 0, 200)
for i := 0; i < numbers; i++ {
bytes = append(bytes, strconv.Itoa(i)...)
}
_ = string(bytes)
}
b.StopTimer()
}
func BenchmarkRunes2(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
runes := make([]rune, 0, 200)
for i := 0; i < numbers; i++ {
runes = append(runes, []rune(strconv.Itoa(i))...)
}
_ = string(runes)
}
b.StopTimer()
}
性能测试
拼接1~100 读取1次
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 272563 4254 ns/op
BenchmarkSprintf-16 107931 10988 ns/op
BenchmarkBytesBuf-16 1616385 749.9 ns/op
BenchmarkStringBuilder-16 1935043 622.3 ns/op
BenchmarkBytes-16 1818974 689.7 ns/op
BenchmarkRunes-16 479460 2465 ns/op
BenchmarkStringBytes2-16 2806760 433.3 ns/op
BenchmarkRunes2-16 647060 1841 ns/op
拼接1~100 读100次
goos: windows
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 289051 4169 ns/op
BenchmarkSprintf-16 109444 11122 ns/op
BenchmarkBytesBuf-16 376160 3263 ns/op
BenchmarkStringBuilder-16 1845380 643.5 ns/op
BenchmarkBytes-16 380598 3168 ns/op
BenchmarkRunes-16 25981 45550 ns/op
BenchmarkStringBytes2-16 444042 2889 ns/op
BenchmarkRunes2-16 26660 44863 ns/op
拼接1~10000 读取1次
goos: windows
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 56 22105196 ns/op
BenchmarkSprintf-16 45 26228936 ns/op
BenchmarkBytesBuf-16 5314 216823 ns/op
BenchmarkStringBuilder-16 5709 206068 ns/op
BenchmarkBytes-16 5842 208099 ns/op
BenchmarkRunes-16 1687 631374 ns/op
BenchmarkStringBytes2-16 6655 191255 ns/op
BenchmarkRunes2-16 2181 548359 ns/op
拼接1~10000 读10000次
goos: windows
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 52 21811823 ns/op
BenchmarkSprintf-16 45 26745293 ns/op
BenchmarkBytesBuf-16 54 23539459 ns/op
BenchmarkStringBuilder-16 6030 205297 ns/op
BenchmarkBytes-16 50 25721030 ns/op
BenchmarkRunes-16 2 862442350 ns/op
BenchmarkStringBytes2-16 50 26813260 ns/op
BenchmarkRunes2-16 2 867332600 ns/op
拼接1~1000000 读取1次
goos: windows
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 0 bomm ns/op
BenchmarkSprintf-16 0 bomm ns/op
BenchmarkBytesBuf-16 45 26077220 ns/op
BenchmarkStringBuilder-16 45 27934771 ns/op
BenchmarkBytes-16 43 28071884 ns/op
BenchmarkRunes-16 15 75816607 ns/op
BenchmarkStringBytes2-16 54 22541828 ns/op
BenchmarkRunes2-16 16 64386250 ns/op
拼接1~1000000 读取1000000次
goos: windows
goarch: amd64
pkg: learn/basic/prof/string
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkStringAdd-16 0 bomm ns/op
BenchmarkSprintf-16 0 bomm ns/op
BenchmarkBytesBuf-16 0 bomm ns/op
BenchmarkStringBuilder-16 44 27153045 ns/op
BenchmarkBytes-16 0 bomm ns/op
BenchmarkRunes-16 0 bomm ns/op
BenchmarkStringBytes2-16 0 bomm ns/op
BenchmarkRunes2-16 0 bomm ns/op
总结
大体上拼接性能:
在写多读少的场景下:
预分配空间[]byte>string builder ≈ string buffer>[]byte>预分配空间[]rune>[]rune> + >fmt
在写多读多的场景下:
string builder >> 其他
这里总结优化思路:
- 字符串修改时避免重新分配空间:使用byte切片,尽量预分配大概的空间。
- 获取string时,避免byte切片转化为string重新分配空间