Go怎么做性能优化工具篇之pprof
工欲善其事、必先利其器。这次我们来看看Go的性能优化工具有哪些吧
Go性能优化的工具
一、pprof 工具
pprof 是 Go 语言自带的性能分析工具,可以帮助开发者分析程序的 CPU 使用情况、内存使用情况、goroutine 调度情况等,从而定位性能瓶颈。通过 pprof,开发者可以生成各种性能报告,帮助进行代码优化。
1、pprof 如何使用
首先,你需要在 Go 程序中引入 net/http/pprof 包,这个包自动注册了 HTTP 端点,允许你查看性能数据。
1.1 启动 pprof 服务器
首先,你需要在 Go 程序中引入 net/http/pprof 包,这个包自动注册了 HTTP 端点,允许你查看性能数据。
package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof" // 导入 pprof 包,它会自动注册 HTTP 路由
"time"
"math/rand"
"sync"
"os"
)
var wg sync.WaitGroup
// 模拟一个高负载的函数,进行大量计算
func heavyComputation(n int) int {
time.Sleep(time.Millisecond * 10) // 模拟计算延迟
return n * n
}
// 模拟一个程序
func startTasks() {
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
// 模拟 CPU 密集型任务
heavyComputation(rand.Intn(1000))
}(i)
}
}
func main() {
// 启动 pprof HTTP 服务,监听 6060 端口
go func() {
log.Println("pprof listening on localhost:6060")
log.Fatal(http.ListenAndServe("localhost:6060", nil))
}()
// 启动一些模拟任务
go startTasks()
// 等待任务完成
wg.Wait()
// 打印一些结果
fmt.Println("All tasks completed")
// 模拟一些其他处理
time.Sleep(10 * time.Minute) // 让程序持续运行,供 pprof 分析
}
- 通过导入 net/http/pprof 包,你不需要显式地调用它的函数,包会自动将相关的 HTTP 处理程序注册到默认的 HTTP 路由器上。
- 可以通过访问 http://localhost:6060/debug/pprof/ 来获取性能分析数据。
1.2 proof 字段分析
-
allocs(内存分配采样)
描述:记录程序中所有过去的内存分配。allocs 不是针对某个特定时间点的快照,而是通过定期采样程序的内存分配来获取信息。
用途:帮助分析程序的内存使用情况,找出高频的内存分配和可能的内存泄漏。可以通过查看分配的内存块,了解哪些部分的代码导致了过多的内存分配。
示例:通过 pprof 你可以获取内存分配的历史采样数据,找到内存使用的瓶颈。 -
block(阻塞分析)
描述:记录导致阻塞的栈跟踪信息,通常与同步原语(如 sync.Mutex、sync.Cond 等)相关。阻塞是指某个 goroutine 因等待锁、条件变量等原因无法继续执行。
用途:帮助分析程序中阻塞的 goroutine,尤其是锁争用和并发问题。通过分析 block profile,你可以查看哪些同步操作导致了程序性能下降。
示例:如果程序存在多个 goroutine 并发访问共享资源且存在锁竞争,block profile 会显示这些锁争用和阻塞的堆栈信息。 -
cmdline(命令行)
描述:记录当前程序的命令行启动参数。包括程序的所有命令行参数。
用途:有助于了解当前运行的程序是如何启动的,哪些命令行参数被传递给程序。这对分析程序执行环境和上下文非常重要。
示例:可以帮助你识别程序的启动方式,比如使用了哪些配置文件、环境变量或特定的运行参数。 -
goroutine(goroutine栈分析)
描述:记录当前所有 goroutine 的栈跟踪信息。每个 goroutine 可能处于不同的状态(例如:运行中、等待中或阻塞中)。
用途:帮助分析程序中的并发行为,尤其是追踪那些长时间运行或阻塞的 goroutine。可以帮助发现死锁、锁竞争等并发问题。
示例:当程序有大量 goroutine 时,通过查看 goroutine profile,你可以看到所有 goroutine 的调用栈,并找出哪些 goroutine 在等待、阻塞或占用大量资源。 -
heap(堆内存分析)
描述:对程序中当前存活的对象进行内存分配采样。heap profile 给出了程序在某一时刻堆上所有活跃对象的内存使用情况。
用途:用于分析堆内存的使用情况,特别是帮助识别内存泄漏或不必要的内存分配。通过 heap profile,开发者可以找出程序中使用最多内存的对象。
示例:如果程序内存消耗过高,使用 heap profile 可以帮助找出内存使用最多的地方,或者确认是否存在未被释放的内存。 -
mutex(互斥锁分析)
描述:记录持有并争用互斥锁的 goroutine 栈跟踪。mutex profile 显示了锁争用情况,包括哪些 goroutine 当前持有锁,哪些 goroutine 在等待锁。
用途:帮助分析程序中的锁竞争和同步问题。对于性能瓶颈,特别是锁争用较为严重的程序,可以使用 mutex profile 来找出锁的争用点。
示例:如果有多个 goroutine 在争夺同一个锁,mutex profile 会显示哪个 goroutine 持有锁,哪些正在等待,从而帮助开发者优化锁的使用。 -
profile(CPU分析)
描述:记录程序的 CPU 性能分析数据。可以指定采样的持续时间(通过 GET 参数中的 duration),profile 是常用的性能分析工具,帮助定位 CPU 的瓶颈。
用途:用于分析程序的 CPU 使用情况,找出消耗大量 CPU 时间的函数或代码路径。常见的用途是分析程序中消耗最多时间的部分,并进行性能优化。
示例:通过 profile,你可以获得一个 CPU profile 文件,并使用 go tool pprof 命令来分析哪个函数消耗了最多的 CPU 时间。 -
threadcreate(线程创建分析)
描述:记录导致创建新操作系统线程的栈跟踪。操作系统线程是由 Go 运行时系统管理的线程,Go 会根据需要动态创建操作系统线程。
用途:帮助分析程序中是否存在大量的线程创建,可能影响性能。过多的线程创建可能导致上下文切换频繁,从而影响程序的整体性能。
示例:通过 threadcreate profile,你可以发现哪些操作会触发新的线程创建,并查看线程的堆栈信息。 -
trace(程序执行追踪)
描述:记录程序的执行轨迹。通过 trace,可以跟踪程序的所有执行过程,详细记录 goroutine 的调度、阻塞等事件。
用途:帮助分析程序的执行流程,查看 goroutine 的调度情况,尤其是在并发程序中,分析并发和同步的问题。
示例:使用 trace 你可以查看程序在特定时间段内的所有调度事件,包括 goroutine 的启动、调度、阻塞等情况。
allocs、heap 和 profile 主要用于内存和 CPU 性能分析,帮助找出内存泄漏、过度分配和 CPU 密集型操作。
block、mutex 和 threadcreate 主要用于并发分析,帮助找出锁竞争、阻塞和线程创建问题。
goroutine 和 trace 则帮助分析程序的并发行为和执行过程。
我们也可以不通过上面的页面触发性能数据采集,而是直接访问 url 并添加参数来控制采集的时长,就像下面这样
curl "http://127.0.0.1:6060/debug/pprof/profile?seconds=30" > profile.pprof
在这些采样类型里,常用的有下面两类:
profile 采样,也就是 cpu 采样,用于确定程序中哪些函数或代码片段在运行时消耗了大量的 CPU 时间,帮助定位 CPU 性能瓶颈。
内存分配采样,具体包括 allocs 采样和 heap 采样。其中,allocs 采样侧重于定位那些频繁进行内存分配的函数,而 heap 采样用于查看存活对象的内存分配情况,侧重于定位内存泄漏问题。
1.3 火焰图
火焰图(Flame Graph)是性能分析的一个可视化工具,能够帮助你快速识别程序中最耗时的部分。在 Go 中,火焰图常常与 pprof 配合使用,用于分析程序的 CPU 性能。通过火焰图,你可以轻松地看到程序的函数调用栈,查看每个函数的执行时间,从而发现性能瓶颈。
- 使用 pprof 收集 CPU 配置
在 Go 中,你可以通过 net/http/pprof 包来生成性能分析数据。首先确保你的 Go 程序包含了 pprof。
导入 net/http/pprof 包
package main
import (
"fmt"
"math/rand"
"net/http"
_ "net/http/pprof" // 引入pprof包来启用性能分析
"os"
"time"
)
func main() {
// 启动pprof HTTP server
go func() {
fmt.Println("Starting pprof server on :6060...")
if err := http.ListenAndServe(":6060", nil); err != nil {
fmt.Println("Error starting pprof server:", err)
}
}()
// 模拟一些计算任务
for {
// 模拟一些负载
rand.Seed(time.Now().UnixNano())
n := rand.Intn(100000)
_ = fib(n) // 调用一个计算密集型函数
time.Sleep(time.Second) // 稍作等待
}
}
// fib 是一个计算 Fibonacci 数列的函数,模拟计算密集型任务
func fib(n int) int {
if n <= 0 {
return 0
}
if n == 1 {
return 1
}
return fib(n-1) + fib(n-2)
}
- 获取 CPU 配置数据
要生成火焰图,首先需要从程序中获取 CPU 配置数据。你可以通过向 pprof 提供一个请求来获取 CPU 配置:
访问 URL http://localhost:6060/debug/pprof/profile?seconds=30,这里的 seconds=30 表示你希望记录 30 秒的 CPU 样本。
wget http://localhost:6060/debug/pprof/profile?seconds=30 -O cpu.pprof
这会将 CPU 配置样本保存到一个文件中,名为 cpu.pprof。
-
安装 graphviz 工具
-
生成火焰图
使用 go tool pprof 来处理 cpu.pprof 文件,并将其转换为火焰图。首先安装 go tool pprof:
go get github.com/google/pprof
然后,使用 go tool pprof 生成一个 .svg 格式的火焰图。
在命令行中使用 go tool pprof 进行分析:
go tool pprof cpu.pprof
你会进入一个交互式的 pprof shell,在这个 shell 中,你可以使用 svg 命令(需要先安装graphviz 工具)来生成火焰图:
(pprof) svg
这将生成一个 .svg 文件,保存为 profile.svg。你可以用浏览器打开它查看火焰图。
我们也可以使用可视化界面来查看。Golang 提供了可视化展示工具 pprof,我们可以通过下面的命令,启动一个 Web 界面查看(可视化界面需要提前安装 graphviz)。
go tool pprof -http :8889 cpu.pprof
通过这里可以查看具体的代码。