共计 2618 个字符,预计需要花费 7 分钟才能阅读完成。
Benchmark(基准测评)
参考文章:https://blog.logrocket.com/be…
字符串拼接性能及其原理
Go 官网中举荐应用 StringBuffer 来拼接字符串
在 Go 语言中,字符串是不可变的,拼接字符串事实上是创立了一个新的字符串变量。可想而知,如果大量应用 ”+” 运算符,则程序的性能将大打折扣。string 底层应用了 byte 数组 …
常见的拼接形式
https://geektutu.com/post/hpg…
1 应用 +
func plusConcat(n int, str string) string {
s := ""
for i := 0; i < n; i++ {s += str}
return s
}
2 应用 fmt.Sprintf
func sprintfConcat(n int, str string) string {
s := ""
for i := 0; i < n; i++ {s = fmt.Sprintf("%s%s", s, str)
}
return s
}
3 应用 bytes.Buffer
func bufferConcat(n int, s string) string {buf := new(bytes.Buffer)
for i := 0; i < n; i++ {buf.WriteString(s)
}
return buf.String()}
4 应用 []byte
func byteConcat(n int, str string) string {buf := make([]byte, 0)
for i := 0; i < n; i++ {buf = append(buf, str...)
}
return string(buf)
}
5 应用 strings.Builder
func builderConcat(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {builder.WriteString(str)
}
return builder.String()}
如果长度是可预知的,那么创立 []byte 时,咱们还能够预调配切片的容量(cap)。
func preByteConcat(n int, str string) string {buf := make([]byte, 0, n*len(str))
for i := 0; i < n; i++ {buf = append(buf, str...)
}
return string(buf)
}
benchmark 性能比拼
每个 benchmark 用例中,生成了一个长度为 10 的字符串,并拼接 1w 次。
func benchmark(b *testing.B, f func(int, string) string) {var str = randomString(10)
for i := 0; i < b.N; i++ {f(10000, str)
}
}
func BenchmarkPlusConcat(b *testing.B) {benchmark(b, plusConcat) }
func BenchmarkSprintfConcat(b *testing.B) {benchmark(b, sprintfConcat) }
func BenchmarkBuilderConcat(b *testing.B) {benchmark(b, builderConcat) }
func BenchmarkBufferConcat(b *testing.B) {benchmark(b, bufferConcat) }
func BenchmarkByteConcat(b *testing.B) {benchmark(b, byteConcat) }
func BenchmarkPreByteConcat(b *testing.B) {benchmark(b, preByteConcat) }
运行该用例:
$ go test -bench="Concat$" -benchmem .
goos: darwin
goarch: amd64
pkg: example
BenchmarkPlusConcat-8 19 56 ms/op 530 MB/op 10026 allocs/op
BenchmarkSprintfConcat-8 10 112 ms/op 835 MB/op 37435 allocs/op
BenchmarkBuilderConcat-8 8901 0.13 ms/op 0.5 MB/op 23 allocs/op
BenchmarkBufferConcat-8 8130 0.14 ms/op 0.4 MB/op 13 allocs/op
BenchmarkByteConcat-8 8984 0.12 ms/op 0.6 MB/op 24 allocs/op
BenchmarkPreByteConcat-8 17379 0.07 ms/op 0.2 MB/op 2 allocs/op
PASS
ok example 8.627s
从基准测试的后果来看,应用 + 和 fmt.Sprintf 的效率是最低的,和其余的形式相比,性能相差约 1000 倍,而且耗费了超过 1000 倍的内存。当然 fmt.Sprintf 通常是用来格式化字符串的,个别不会用来拼接字符串。
strings.Builder、bytes.Buffer 和 []byte 的性能差距不大,而且耗费的内存也非常靠近,性能最好且耗费内存最小的是 preByteConcat,这种形式预调配了内存,在字符串拼接的过程中,不须要进行字符串的拷贝,也不须要调配新的内存,因而性能最好,且内存耗费最小。
Go 实现汇合(Set)
使用了空构造体的个性
Go 语言规范库没有提供 Set 的实现,通常应用 map 来代替。事实上,对于汇合来说,只须要 map 的键,而不须要值。即便是将值设置为 bool 类型,也会多占据 1 个字节,那假如 map 中有一百万条数据,就会节约 1MB 的空间。
因而呢,将 map 作为汇合 (Set) 应用时,能够将值类型定义为空构造体,仅作为占位符应用即可。
type Set map[string]struct{}
func (s Set) Has(key string) bool {_, ok := s[key]
return ok
}
func (s Set) Add(key string) {s[key] = struct{}{}
}
func (s Set) Delete(key string) {delete(s, key)
}
func main() {s := make(Set)
s.Add("Tom")
s.Add("Sam")
fmt.Println(s.Has("Tom"))
fmt.Println(s.Has("Jack"))
}