这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
主要是学习影响性能的因素和接触工具(之前没认真学过),所以写的东西非常入门
性能优化建议
简介
- 性能优化的前提是满足正确可靠、简洁清晰等质量因素
- 性能优化是综合评估,有时候时间效率和空间效率可能对立
Benchmark
-
性能的表现要用数据衡量
-
go test -bench=. benchmem -
结果说明:
Slice
Slice 预分配内存
-
尽可能在使用
make()初始化切片时提供容量信息
大内存未释放
在已有切片的基础上创建切片,不会更新底层数组
可以使用 copy 代替 re-slice
//re-slice
func getLastBySlice(origin []int) []int {
return origin[len(origin) - 2:]
}
// copy
func getLastByCopy(origin []int ) []int {
result := make([]int, 2)
copy(result, origin[len(origin) - 2:])
return result
}
Map
- 不断向 map 中添加元素的操作会触发 map 的扩容,影响性能
- 可以提前分配空间,以减少内存拷贝和 Rehash 的消耗
- 建议根据实际需求提前预估需要的空间
String
String 的拼接是有讲究的
建议使用 strings.Builder
-
使用 + 拼接的性能是最差的,strings.Builder, bytes.Buffer 相近,而 strings.Builder 更快
-
分析
-
字符串在 Go 语言中是不可变类型,故其占用的内存大小是固定的
- 每次使用 + 都会重新分配内存
-
strings.Builder 和 bytes.Builder 底层都是 []byte 数组
- 由于其内存扩容策略,不需要每次拼接都重新分配内存
-
空结构体
- 空结构体示例不占用任何的内存空间
- 可以作为各种场景下的占位符使用
Atomic 包
在多线程场景中可以维护一个变量:
- 锁的实现是通过操作系统来实现的,属于系统调用
- atomic 操作是通过硬件实现的,效率比锁高
- sync.Mutex 应该用于保护一段逻辑,而不仅仅用于保护一个变量
- 对于非数值操作,可以使用 atomic.Value,其能承载一个interface{}
小结
- 避免常见的性能陷阱
- 普通应用代码不要一味追求性能
- 越高级的优化手段越容易出现问题
- 在满足正确可靠、简洁清晰的质量要求的前体现提高程序的性能
疑问
- Atomic包是通过硬件实现的,而Go语言是一个跨平台的语言,它是怎么保证各个平台都能够成功运行的
性能优化分析工具
说在前面
- 性能优化要依据数据
- 要定位最大的瓶颈
- 不要过早优化:当产品迭代时,代码可能会被优化
- 不要过度优化:可能出现无法兼容的问题
性能分析工具pprof
说明
- 可以获取什么地方消耗了多少 CPU、Memory
- 可视化
功能简介
GUI
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/..."
如果出现 Could not execute dot; may need to install graphviz.报错,先前往[graphviz的官网](Download | Graphviz)下载并安装 graphviz
Source 视图
搜索相关问题函数
top指令
对于 CPU 的分析执行 top 指令后会返回如下几个参数:
- flat => 当前函数本身的执行耗时
- flat% => flat 占 CPU 总时间的比例
- sum% => 上面每一行的 flat% 总和
- cum => 当前函数本身加上其调用函数的总耗时
- cum% => cum 占 CPU 总时间的比例
Heap
- alloc_objects 累计申请的对象数
- alloc_space 累计申请的内存大小
- inuse_objects 当前持有的对象数
- inuse_space 当前占用的内存大小
Goroutine
火焰图:
- 由上至下表示调用顺序
- 每一个块表示一个函数,块越长表示占用CPU时间越长
- 火焰图是动态的
其他问题
- Mutex (锁)
- Block (阻塞)
- ……