Go语言性能调优实战案例-工程实践 | 青训营笔记
参加 [第六届青训营] 笔记创作第五篇
性能调优课的内容
分为3个部分:
- 第一个部分是对性能调优常见的方面进行简介讲解
- 第二个部分介绍了Go语言用于性能分析的工具pprof
- 第三个部分是使用pprof来进行实战调优的实例
Go语言性能调优
性能评估简介
Go语言提供了基准性能测试的 benchmark 工具,Benchmark是Go语言中用于性能测试和比较的工具。它可以帮助我们评估代码的执行速度和资源消耗,并提供详细的结果报告。使用代码如下:
go test -bench=. -benchmen
-
在测试文件中,定义一个以Benchmark开头的函数,函数签名为func BenchmarkXxx(b *testing.B),其中Xxx是被测试的函数名。
-
在Benchmark函数中,使用b.N来获取测试的迭代次数,然后编写需要测试的代码。
性能优化之slice
slice方面的性能优化主要是对内存的预分配以及管理内存的释放。
slice结构如下:
type slice struct {
array unsafe.Pointer // 底层数组的指针
len int // 长度
cap int // 容量
}
当切片中元素个数大于切片大小时会发生扩容操作。扩容操作花费一定时间,浪费性能,为了节省这部分性能开销,如果知道具体容量,就应该预先设置容量大小。
切片中在创建小的切片,小的切片不会复制大切片的底层数组,而是选择公用一个底层数组,使用新的指针指向合适的位置。但是当这样一个场景,已经存在大的切片 a := [1:10000]int,这时候使用 a[1:3] 创建一个小的切片,这个切片会引用大的切片的底层数组(该数组的大小10000),进行垃圾回收的时候,会分析引用关系,因为小的切片这2个int的空间,10000int的数组空间被引用着,不能释放。这就造成系统资源的极大浪费。
这个问题可以通过使用copy代替re-slice来得到解决。
性能优化之Map
与slice相似,map也有着合理预分配空间可以优化性能的特性。因为不断往Map中put元素,会引发扩容,如果提前分配,可以减少rehash、分配内存的开销。
可以使用make函数在创建map的时候就指定初始容量。 对所需要用到的Map最大容量进行大致预估,并将其作为参数传递给make函数。
func createMap() {
m := make(map[int]string)
for i := 0; i < 10000; i++ {
m[i] = fmt.Sprintf("value%d", i)
}
}
Benchmark测试:
func BenchmarkCreateMap(b *testing.B) {
for i := 0; i < b.N; i++ {
createMap()
}
}
pprof工具使用实战
性能调优原则
- 依靠数据而非猜测
- 定位最大瓶颈而非细枝末节
- 不需要过早的进行优化
- 考虑优化成本,不需要过度
配置
使用pprof需要在程序中导入net/http/pprof包,并在代码中添加相应的路由处理函数。例如,可以在主函数中添加以下代码来启用pprof:
import _ "net/http/pprof"
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
terminal启动了项目源码后,再开一个terminal并输入以下采样代码执行
go tool pprof "http://127.0.0.1:6060/debug/pprof/profile?seconds=10"
执行后使用top命令查看cpu性能分析报告可以看到:
参数解释:
- flat 当前函数的本身的执行耗时
- flat% flat 占 cpu 总时间的比例
- sum% 上面每一行的 flat% 综合
- cum 指当前函数本身加上其调用函数的总耗时
- cum% cum 占 CPU 总时间的比例
当flat == cum时,根据定义可知当前函数并没有调用其他函数。 相对的,当flat == 0时,那么函数中只有其他函数的调用。
由top看到了eat函数占用cpu较多,故这里用list指定罗列出eat函数的源代码:
可以使用web命令进行网页可视化显示,例如如果要查看内存的可视化情况,可以输入以下代码:
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
要看cpu的情况其实把最后的heap改为cpu即可
其他要看协程之类的其实也一样,改为block,goroutine等等,分析思路也是一样的:
- 流程图里分析找到占用最大资源的函数
- 通过source找到具体行
- 注释掉具体行后运行。