性能优化指南|青训营

61 阅读2分钟

性能优化

性能优化的原则

  • 要依靠数据不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

1.benchmark

运行fib目录下的fib_test.go img.png

  • BenchmarkFib10-8中的-8即GOPMAXPROCS,在go1.5版本后默认等于cpu核数
  • 3389920表示总共执行的次数,即b.N的值
  • 388.1 ns/op表示每次执行耗时
  • 0 B/op表示每次执行申请的内存大小
  • 0 allocs/op表示每次执行分配内存的次数

或者在终端运行

go test -bench=. -benchmem
  • -bench=.表示在当前目录进行基准测试
  • -benchmem表示统计内存信息

2.Slice

在终端运行

go test -bench="Alloc$" -benchmem
  • "-bench=Alloc$"表示测试对象只包括以Alloc结尾的
  • "-benchmem"表示统计内存信息

测试结果如下 img_1.png 两种策略每次执行所耗费的时间差距较大

  • 预分配策略使得执行速度提高4倍多
  • 预分配策略每次执行所申请的内存大小仅为81920B,而没有预分配的策略每次执行申请的内存达到357627B
  • 预分配策略每次执行只申请1次内存,而没有预分配的策略每次执行要申请19次内存之多

3.map

在slicec_test.go中添加如下代码

func BenchmarkNoPreAllocMap(b *testing.B) {
	for i := 0; i < b.N; i++ {
		NoPreAllocMap(10000)
	}
}
func BenchmarkPreAllocMap(b *testing.B) {
	for i := 0; i < b.N; i++ {
		PreAllocMap(10000)
	}
}

在终端运行

 go test -bench="AllocMap" -benchmemgo test -bench="AllocMap" -benchmem

测试结果如下 img_2.png

4.string

比较三种字符串拼接方式的性能

  • "+" img_3.png
  • strings.Builder img_5.png
  • bytes.Buffer img_4.png
  • string
    • golang将string类型分配到只读内存段,因此不能通过下标的方式对内容进行修改
    • 多个string变量可共用统一字符串的某个部分,即多个string的data域指向同一块内存空间的某个位置
    • 如需改动字符串的内容,需要开辟新的内存空间
  • "+"
    • 每次都会重新分配内存
  • strings.Builder(最快)
    • 底层是是[]byte数组,内存扩容策略,拼接时,不需要重新分配内存
    • 直接将[]byte数组转换为string类型返回
  • bytes.Buffer
    • 底层是是[]byte数组,内存扩容策略,拼接时,不需要重新分配内存
    • 转化为string类型时,会重新申请空间

预分配 进一步优化

Grow(n * len(str))

img_6.png img_7.png

5.空结构体

img_8.png

  • 空结构体struct{}不占用内存空间
  • 可作为各种场景下的占位符使用
    • 节省资源
    • 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符使用
  • 实现set,可以考虑使用map[interface{}]struct{}
    • 对于这种场景,只需要map的key,而不需要value
    • 即使将value设置为bool类型,也会占用1个字节的内存空间

6.atomic包

img_18.png

  • mutex机制
    • 操作系统实现,属于系统调用
    • sync.Mutex应该用来保护一段逻辑,而不是保护一个变量
  • atomic机制
    • 硬件实现,效率比锁高
    • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}类型的值