我将程序调优的过程总结为发现问题和解决问题两个步骤。
导致程序有性能问题的具体原因往往各式各样,但性能问题的种类是往往固定的几种。 《Go程序性能调优工具pprof介绍》介绍了程序常见的性能问题有:cpu、内存占用过高,协程数量过多,有不必要的锁竞争和阻塞。本文使用一个已知有cpu占用过高问题的程序进行调优实战。
在《Go程序性能调优工具pprof介绍》一文中,对pprof工具进行了简单的介绍,并总结了常用的命令。本文将使用pprof工具对一个go程序进行实际的性能调优。
01 发现问题
对于一个未知问题的程序,我们应该对上文提到的可能的性能问题进行逐一的测试,但由于本文使用的程序已知有且仅有cpu占用问题,所以直接分析cpu占用的问题即可。
修改程序
在主函数中,添加:
import {
"net/http"
_ "net/http/pprof"
"log"
}
func main() {
go func() {
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
...
}
命令行工具
1.开启cpu命令行工具
go tool pprof http://localhost:6060/debug/pprof/profile
2.使用top工具,查看是哪一个函数的cpu占用较高:
(pprof)top
可以看到func1.Func1函数执行耗时占据了总耗时的74.19%,func2.Func2函数占据了21.54%。
这里有一个问题,我们看看Func1函数和Func2函数:
package main
func main() {
for {
func2.Func2()
func1.Func1()
}
}
package func1
func Func1() {
loop := 100
for i := 0; i < loop; i++ {
// do nothing
}
}
package func2
func Func2() {
loop := 50
for i := 0; i < loop; i++ {
// do nothing
}
}
可以看到Func1的每次执行的循环次数是Func2的两倍,直观上Func1的cpu占用也应该是Func2的两倍才对,但从top的结果来看并不是这样的。
尝试将Func1和Func1的调用顺序调换,再使用top工具:
for {
func1.Func1()
func2.Func2()
}
可以看见Func2函数的占用来到了接近50%,二者之间仍然不是接近二分之一的关系。
多次测试,汇总结果如下:
3.再使用list工具,查看func1.Func1中具体是哪些代码占据了大量的cpu资源:
(pprof)list func1.Func1
可视化工具
(pprof) web
通过调用关系图,可以更为直观的看到各个函数的执行耗时占比,可以直观地看出func1包的Func1函数是cpu占用最高的。
图上各个框中显示的数据是执行函数本身的耗时和耗时占比(即不包括其调用的函数的耗时)。
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile"
(pprof)http=:8080 "http://localhost:6060/debug/pprof/profile"
火焰图:
02 解决问题
我们现在定位到了存在性能问题的具体代码段,在实际程序开发中,我们需要分析定位到的代码段,思考是否有更好的实现方式来降低该功能的时间复杂度从而降低cpu占用率。
03 阅读
网络上还有一些文章记录了使用pprof进行程序调优的过程,十分值得我们学习:
go.dev/blog/pprof