性能优化
性能优化的原则
- 要依靠数据不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
1.benchmark
运行fib目录下的fib_test.go
- 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"表示统计内存信息
测试结果如下
两种策略每次执行所耗费的时间差距较大
- 预分配策略使得执行速度提高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
测试结果如下
4.string
比较三种字符串拼接方式的性能
- "+"
- strings.Builder
- bytes.Buffer
- string
- golang将string类型分配到只读内存段,因此不能通过下标的方式对内容进行修改
- 多个string变量可共用统一字符串的某个部分,即多个string的data域指向同一块内存空间的某个位置
- 如需改动字符串的内容,需要开辟新的内存空间
- "+"
- 每次都会重新分配内存
- strings.Builder(最快)
- 底层是是[]byte数组,内存扩容策略,拼接时,不需要重新分配内存
- 直接将[]byte数组转换为string类型返回
- bytes.Buffer
- 底层是是[]byte数组,内存扩容策略,拼接时,不需要重新分配内存
- 转化为string类型时,会重新申请空间
预分配 进一步优化
Grow(n * len(str))
5.空结构体
- 空结构体struct{}不占用内存空间
- 可作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符使用
- 实现set,可以考虑使用map[interface{}]struct{}
- 对于这种场景,只需要map的key,而不需要value
- 即使将value设置为bool类型,也会占用1个字节的内存空间
6.atomic包
- mutex机制
- 操作系统实现,属于系统调用
- sync.Mutex应该用来保护一段逻辑,而不是保护一个变量
- atomic机制
- 硬件实现,效率比锁高
- 对于非数值操作,可以使用atomic.Value,能承载一个interface{}类型的值