Go 库性能分析工具pprof
作者:小马别过河 发布时间:2024-02-13 20:55:27
场景
我们一般没必要过度优化 Go 程序性能。但是真正需要时,Go 提供的 pprof 工具能帮我们快速定位到问题。比如,我们团队之前有一个服务,在本地和测试环境没问题,一到灰度环境,就报 cpu 负载过高,后经排查,发现某处代码死循环了。我把代码简化成如下:
// 处理某些业务,真实的代码中这个死循环很隐蔽
func retrieveSomeThing() {
for {}
}
// 处理其他的一些业务,无意义,用于后续做例子
func doSomeThing() {
do1()
for i := 0; i < 200000000; i++ {}
do2()
}
// 无意义
func do1() {
for i := 0; i < 200000000; i++ {}
}
// 无意义
func do2() {
for i := 0; i < 200000000; i++ {}
}
func main() {
go retrieveSomeThing()
go doSomeThing()
// 阻塞一下
time.Sleep(3 * time.Second)
}
解决问题前,先介绍下 pprof。
pprof
pprof 包会输出运行时的分析数据(profiling data),这些数据可以被 pprof 的可视化工具解析。Go 标准库主要提供了两个包:
runtime/pprof
通过写入到文件的方式暴露 profile 数据;net/http/pprof
通过 http 服务暴露 profile 数据,适用于守护进程。
生成 profile 文件
CPU 性能分析
在 runtime/pprof
中,使用StartCPUProfile
开启 CPU 性能分析。退出程序前,需要调用StopCPUProfile
把采样数据 flush 到输出文件。
采样的频率默认是 100 Hz(每秒 100 次)。
// 输出到标准输出,一般是指定文件
if err := pprof.StartCPUProfile(os.Stdout); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
内存性能分析
调用 WriteHeapProfile
开启内存性能分析:
// 输出到标准输出,一般是指定文件
if err := pprof.WriteHeapProfile(os.Stdout); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}
分析 profile 文件 && 优化代码
以开篇的代码为例,由于是 CPU 过载,我们可以在 main 函数开启 CPU Profile:
// 通过参数指定 cpu profile 输出的文件
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
// 开启 CPU 分析
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
// 业务代码
go retrieveSomeThing()
go doSomeThing()
// 模拟阻塞
time.Sleep(5 * time.Second)
}
我们执行命令,输出 profile 文件到 cpu.prof。
go run main.go -cpuprofile cpu.prof
go tool pprof
Go 提供性能解析工具:go tool pprof
。我们使用 go tool 打开 profile 文件。
> go tool pprof cpu.prof
Type: cpu
Time: Nov 16, 2022 at 1:40pm (CST)
Duration: 5.17s, Total samples = 4.54s (87.75%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
这是个交互式的界面,输入help
可以查看所有命令。
top 命令
我们使用 topN 命令,查看根据 flat 从大到小排序的前 N 条数据。
(pprof) top10
Showing nodes accounting for 4650ms, 100% of 4650ms total
flat flat% sum% cum cum%
4220ms 90.75% 90.75% 4450ms 95.70% main.retrieveSomeThing
230ms 4.95% 95.70% 230ms 4.95% runtime.asyncPreempt
80ms 1.72% 97.42% 200ms 4.30% main.doSomeThing
70ms 1.51% 98.92% 70ms 1.51% main.do2 (inline)
50ms 1.08% 100% 50ms 1.08% main.do1 (inline)
top 命令返回数据有5个指标:
flat : 本函数占用的 CPU 时间,不包括调用函数的时间;
flat% : flat 占的百分比;
sum% : 前面 flat% 的总和;
cum : 累计时间,包括调用的函数的时间;
cum% : cum 的百分比。
以main.doSomeThing
(排第三的函数)为例子,耗时为:
func doSomeThing() { // flat: 80ms cum: 200ms
do1() // 执行时间 50ms
for i := 0; i < 200000000; i++ {} // 执行时间 80ms
do2() // 执行时间 70ms
}
doSomeThing
的 flat 的值为:
for i := 0; i < 200000000; i++ {}的执行时间(80ms),不包括do1和do2的时间。
doSomeThing
的 cum 的值为:
cum(200ms) = doSomething的flat(80ms) + do1的flat(50ms) + do2的flat(70ms)
ps: top 可以使用 -cum 参数来指定,根据 cum 排序。
list 命令
明白了 top 的指标的意思,我们关注到,排在 top1 的函数是 retrieveSomeThing。可以使用 list 命令,查看 retrieveSomeThing 耗时:
(pprof) list retrieveSomeThing
Total: 4.65s
ROUTINE ======================== main.retrieveSomeThing in /xxxx/pprof_note/pprof/main.go
4.22s 4.45s (flat, cum) 95.70% of Total
10ms 10ms 1:package main
. . 2:
. . 3:import (
. . 4: "flag"
. . 5: "log"
. . 6: "os"
. . 7: "runtime/pprof"
. . 8: "time"
. . 9:)
. . 10:
. . 11:// 处理某些业务,真实的代码中这个死循环很隐蔽
. . 12:func retrieveSomeThing() {
4.21s 4.44s 13: for {
. . 14: }
. . 15:}
. . 16:
. . 17:// 处理其他的一些业务,无意义,用于后续做例子
. . 18:func doSomeThing() {
我们定位到13行需要优化。
来源:https://juejin.cn/post/7166518730514497543
猜你喜欢
- 一、变量的定义 mysql中变量定义用declare来定义一局部变量,该变量的使用范围只能在begin...end 块中使用,变量必须定义在
- 刚才显示数据的时候遇到一个日期里面带T的问题,就是天数跟小时数之间出现了一个T。 表字段里面也没有这个T,后来查询度娘,是因为json处理的
- 需求分析:项目中根据测得的数据在界面上实时绘制运行环境:Python 3.7 + Matplotlib 3.0.2 + PyQt 5matp
- 话说本来我的电脑有个2000的数据库,去年我在那个电脑上新装了一个2005的数据库。前不久我买了台新电脑,装了数据库2008 将在旧电脑上的
- <!doctype html><html><head><meta http-equiv
- IPython + ptpython,完美体验首先是安装pip install ipython ptpython然后使用ptipython有
- 前言1.装饰器本质是一个语法糖,是对被装饰方法或类进行的功能扩充,是一种面向切面的实现方法2.装饰器可以分成方法装饰器和类装饰器,他们的区别
- 需求:查询出满足3人及3案有关系的集合# -*- coding: utf-8 -*-from py2neo import Graphimpo
- 一、使用等号查询可以像普通查询使用等号进行查询,但必须查询时间必须和字段对应时间完全相等,比如我要查下面这个值sql如下:SELECT id
- 学习前言看了好多Github,用于保存模型的库都是Keras,我觉得还是好好学习一下的好什么是KerasKeras是一个由Python编写的
- 一直不用这个phpmyadmin,在本机也是用navicat,总感觉phpmyadmin速度较慢。这回不行了,没有独立主机,只好用人家给的p
- 装饰器(decorator)是一种高级Python语法。装饰器可以对一个函数、方法或者类进行加工。在Python中,我们有多种方法对函数和类
- Tkinter库制作记事本现在为了创建这个记事本,你的系统中应该已经安装了 Python 3 和 Tkinter。您可以根据系统要求下载合适
- 有时候需要一次性将SQL Server中的数据导出给其他部门的也许进行关联或分析,这种需求对于SSIS
- 前言特别说明: 本文只适合新手学习这篇文章带我们入门go语言的定义变量的方式,其实和javascript很相似,所以特意总结在此。在go语言
- 1. Python模块和包:一切从基础开始Python模块是一个Python文件,包含一些相关的函数、类或变量的定义,可以通过 i
- 尼姆游戏是个著名的游戏,有很多变种玩法。两个玩家轮流从一堆物品中拿走一部分。在每一步中,玩家可以自由选择拿走多少物品,但是必须至少拿走一个并
- 1、实现 __getitem__(self)class Library(object): def __init__(self):
- Python文字转语音(调研&成品函数)由于项目需要, 我需要将文字转换为语音, 那么第一步就要进行调研什么是语音合成技术?语音合成
- 在TP5公共common.php文件里写<?php //计算某个类别所属的类别层数 function getcatelayer($ca