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"))}