Go程序性能调优实战 | 青训营

119 阅读3分钟

我将程序调优的过程总结为发现问题解决问题两个步骤。

导致程序有性能问题的具体原因往往各式各样,但性能问题的种类是往往固定的几种。 《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

image.png

2.使用top工具,查看是哪一个函数的cpu占用较高:

(pprof)top

image.png 可以看到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()
}

image.png 可以看见Func2函数的占用来到了接近50%,二者之间仍然不是接近二分之一的关系。
多次测试,汇总结果如下:
image.png image.png

3.再使用list工具,查看func1.Func1中具体是哪些代码占据了大量的cpu资源:

(pprof)list func1.Func1

image.png

可视化工具

(pprof) web

image.png 通过调用关系图,可以更为直观的看到各个函数的执行耗时占比,可以直观地看出func1包的Func1函数是cpu占用最高的。
图上各个框中显示的数据是执行函数本身的耗时和耗时占比(即不包括其调用的函数的耗时)。

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile" 

(pprof)http=:8080 "http://localhost:6060/debug/pprof/profile" 

火焰图: image.png

02 解决问题

我们现在定位到了存在性能问题的具体代码段,在实际程序开发中,我们需要分析定位到的代码段,思考是否有更好的实现方式来降低该功能的时间复杂度从而降低cpu占用率。

03 阅读

网络上还有一些文章记录了使用pprof进行程序调优的过程,十分值得我们学习:
go.dev/blog/pprof