关于go:Go高性能

4次阅读

共计 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"))
}
正文完
 0