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: darwingoarch: amd64pkg: exampleBenchmarkPlusConcat-8 19 56 ms/op 530 MB/op 10026 allocs/opBenchmarkSprintfConcat-8 10 112 ms/op 835 MB/op 37435 allocs/opBenchmarkBuilderConcat-8 8901 0.13 ms/op 0.5 MB/op 23 allocs/opBenchmarkBufferConcat-8 8130 0.14 ms/op 0.4 MB/op 13 allocs/opBenchmarkByteConcat-8 8984 0.12 ms/op 0.6 MB/op 24 allocs/opBenchmarkPreByteConcat-8 17379 0.07 ms/op 0.2 MB/op 2 allocs/opPASSok 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"))}