Go pprof 认知到实践

go,pprof · 浏览次数 : 0

小编点评

使用 web 命令可以生成 svg 分析图,方便我们理解代码的性能。 **步骤:** 1. 输入以下命令在浏览器中: ``` go tool pprof -http=:7000 cpu.pprof ``` 2. 浏览器会自动打开一个页面,并显示性能分析图。 **分析图中的含义:** * **线程数**:表示线程数量。 * **CPU时间**:表示 CPU 使用时间。 * **内存分配**:表示内存分配情况。 * **互斥锁**:表示互斥锁占用的时间。 * **阻塞**:表示阻塞时间。 **其他选项:** * `-http` 参数可以启用 web 服务,获取更多性能分析数据。 * `-http=:7000` 参数指定服务地址为 `127.0.0.1:7000`。 * `view` 选项可以查看火焰图 (Flame Graph)。 **注意:** * pprof 采样数据可能会占用一些时间,请耐心等待。 * 性能分析图可能非常大,请确保浏览器有足够的内存才能显示所有数据。

正文

快速开始

测试环境:go version go1.22.2 windows/amd64,源代码开源在 https://github.com/oldme-git/teach-study/tree/master/golang/base/pprof

在正式开始之前,请确保安装 graphviz,这一步不可省略,它可以协助 pprof 生成更直观的数据分析图。可以参考官方网站的安装方法。

go 使用 runtime/pprof 包来对程序进行采样,当然,还有另外一个包 net/http/pprof,这里先按下不表。先来看一个 CPU 分析的例子:

package main

import (
	"math"
	"math/rand"
	"os"
	"runtime/pprof"
)

func main() {
	// 保存 CPU 采样数据
	file := "cpu.pprof"
	os.Remove(file)
	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// 开始采样
	err = pprof.StartCPUProfile(f)
	if err != nil {
		panic(err)
	}
	defer pprof.StopCPUProfile()

	// 测试程序
	for i := 0; i < 1000; i++ {
		nums := genRandomNumbers(10000)
		for _, v := range nums {
			_ = math.Pow(float64(v), rand.Float64())
		}
	}
}

// 测试程序,生成一个随机数切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

这是一个很简单的例子,运行 go run main.go 在当前目录下生成一个 cpu.pprof 文件。然后输入命令 go tool pprof cpu.pprof 进入 pprof 的命令行中。

PS D:\project\teach-study\golang\base\pprof\cpu> go run  main.go        
PS D:\project\teach-study\golang\base\pprof\cpu> go tool pprof cpu.pprof
File: main.exe
Build ID: C:\Users\half\AppData\Local\Temp\go-build787417447\b001\exe\main.exe2024-05-08 11:13:12.7105156 +0800 CST
Type: cpu
Time: May 8, 2024 at 11:13am (CST)
Duration: 1.26s, Total samples = 1.07s (85.20%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

pprof 命令有很多,可以输入 help 查看,不过一般常用的就两个: topweb。输入 top5 可以查看前 5 的耗时调用。

// 输入
top5

// 输出
Showing nodes accounting for 700ms, 65.42% of 1070ms total
Showing top 5 nodes out of 69
      flat  flat%   sum%        cum   cum%
     210ms 19.63% 19.63%      210ms 19.63%  math.archLog
     180ms 16.82% 36.45%      180ms 16.82%  math.archExp
     160ms 14.95% 51.40%      670ms 62.62%  math.pow
      80ms  7.48% 58.88%       80ms  7.48%  internal/chacha8rand.block
      70ms  6.54% 65.42%       70ms  6.54%  math/rand.globalRand

来认识一下这五个指标:

flat 是我们最关注的指标,它代表自身耗时,不包含内部调用。
falt% 自身耗时相对于总耗时的百分比
cum 自身耗时加上内部函数调用的总耗时
cum% 自身耗时加上内部函数调用的总耗时相对于总耗时的百分比
sum% 前 N 行的 flat% 之和。对于上述例子的第四行 58.88=19.63+16.82+14.95+7.48

只是依赖文字无法很好的理解这些指标,我们可以使用 web 命令来生成更直观的 svg 分析图。输入 web 命令后,会自动在浏览器打开 svg

svg 中的每个单元格包含了包名,函数名,flat, flat%, cum, cum%

单元格颜色越红,代表 cum 越大,反之越小;单元格越大,代表 flat 越大,反之越小。单元格之间的箭头线代表调用链,线越粗代表消耗的更多的资源,反之亦然。带有 inline 字段表示该函数被内联进了调用方(当作普通线处理就行)。

函数调用是存在一些固定开销的,例如维护帧指针寄存器BP、栈溢出检测等。因此,对于一些代码行比较少的函数,编译器倾向于将它们在编译期展开从而消除函数调用,这种行为就是内联。

更多的 Web UI

通过 web 命令已经可以获取很直观的性能分析图,我们还可以使用 -http 参数来启用一个 web 服务,获取更多的性能分析。输入 exit 退出 pprof 命令界面,输入命令:

go tool pprof -http=:7000 cpu.pprof

之后会自动在浏览器打开 http://localhost:7000/ui/

view 中可以使用火焰图(Flame Graph),火焰图有新旧两种,可以根据线的长短和颜色判断 CPU 耗时。其他的选项可以点点看看,不复杂,很容易就学会了。

web 服务采样

对于 web 服务的 pprof 采样,我们可以使用基于 runtime/pprof 封装的更便捷的 net/http/pprof 包。

package main

import (
	"fmt"
	"math"
	"math/rand"
	"net/http"
	_ "net/http/pprof"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// 测试程序
		for i := 0; i < 1000; i++ {
			nums := genRandomNumbers(10000)
			for _, v := range nums {
				_ = math.Pow(float64(v), rand.Float64())
			}
		}
		fmt.Fprint(w, "Hello, world!")
	})
	http.ListenAndServe(":8080", nil)
}

// 测试程序,生成一个随机数切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

打开 http://127.0.0.1:8080/debug/pprof/ ,可以看到 pprof  的实时采样数据:

这里面一共有九个采样数据:

allocs 查看历史累计的所有内存分配的采样数据
block 查看历史累计的导致同步原语阻塞的堆栈跟踪
cmdline 包含进程的完整命令行信息,通常用于记录程序启动时的命令行参数
goroutine 实时查看当前所有运行的 goroutines 堆栈跟踪
heap 实时查看活动对象的内存分配情况
mutex 查看历史累计的导致互斥锁的竞争持有者的堆栈跟踪
profile 进行 30s 的 CPU Profiling,浏览器会转圈,30s 后下载一个分析用的 profile 文件
threadcreate 查看创建新 OS 线程的堆栈跟踪
trace 程序执行 trace, 和其他样本数据不同的是,这个需要使用 go tool trace 来分析。trace 是一种更详细的性能分析工具,用于跟踪程序的执行过程,包括函数调用、协程切换等。

默认是不追踪 blockmutex 的,如果需要,在代码中加入这两个:

runtime.SetBlockProfileRate(1) // 开启 block  
runtime.SetMutexProfileFraction(1) // 开启 mutex

这些信息都是实时变化的,刷新一下浏览器即可看见,但是这些信息不易阅读,我们可以把它们下载下来,使用 pprof 分析,以 allocs 为例:

// 下载 allocs 数据
curl -o allocs.pprof http://localhost:8080/debug/pprof/allocs
// pprof
go tool pprof .\allocs.pprof

非 Web 程序的其他采样

在快速开始部分已经介绍了 CPU 采样,对于其他采样,可以参考这段代码:

package main

import (
	"math"
	"math/rand"
	"os"
	"runtime/pprof"
)

func main() {
	// 保存 CPU 采样数据
	file := "allocs.pprof"
	os.Remove(file)
	f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// 测试程序
	for i := 0; i < 1000; i++ {
		nums := genRandomNumbers(10000)
		for _, v := range nums {
			_ = math.Pow(float64(v), rand.Float64())
		}
	}

	pprof.Lookup("allocs").WriteTo(f, 0)
}

// 测试程序,生成一个随机数切片
func genRandomNumbers(n int) []int {
	nums := make([]int, n)
	for i := 1; i < n; i++ {
		nums[i] = rand.Int() * n
	}
	return nums
}

与Go pprof 认知到实践相似的内容:

Go pprof 认知到实践

快速开始 测试环境:go version go1.22.2 windows/amd64,源代码开源在 https://github.com/oldme-git/teach-study/tree/master/golang/base/pprof 在正式开始之前,请确保安装 graphviz,这一步不可

golang pprof 监控系列(4) —— goroutine thread 统计原理

golang pprof 监控系列(4) —— goroutine thread 统计原理 大家好,我是蓝胖子。 在之前 golang pprof监控 系列文章里我分别介绍了go trace以及go pprof工具对memory,block,mutex这些维度的统计原理,今天我们接着来介绍golan

golang trace view 视图详解

> 大家好,我是蓝胖子,在golang中可以使用go pprof的工具对golang程序进行性能分析,其中通过go trace 命令生成的trace view视图对于我们分析系统延迟十分有帮助,鉴于当前对trace view视图的介绍还是很少,在粗略的看过trace统计原理后,我将对这部分做比较详细

每日一库:pprof简介

## pprof简介 `pprof`是Go语言的一个性能分析库,它可以帮助开发者找出程序中的性能瓶颈。`pprof`提供了CPU分析、内存分析、阻塞分析等多种性能分析功能。 以下是`pprof`的主要特性: 1. **CPU分析**:`pprof`可以记录程序在CPU上的运行时间,并将这些数据以火焰

Go语言性能剖析利器--pprof实战

作者:耿宗杰 前言 关于pprof的文章在网上已是汗牛充栋,却是千篇一律的命令介绍,鲜有真正实操的,本文将参考Go社区资料,结合自己的经验,实战Go程序的性能分析与优化过程。 优化思路 首先说一下性能优化的一般思路。系统性能的分析优化,一定是从大到小的步骤来进行的,即从业务架构的优化,到系统架构的优

golang pprof 监控系列(1) —— go trace 统计原理与使用

golang pprof 监控系列(1) —— go trace 统计原理与使用 服务监控系列文章 服务监控系列视频 关于go tool trace的使用,网上有相当多的资料,但拿我之前初学golang的经验来讲,很多资料都没有把go tool trace中的相关指标究竟是统计的哪些方法,统计了哪段

Go微服务开发指南

在这篇深入探讨Go语言在微服务架构中的应用的文章中,我们介绍了选择Go构建微服务的优势、详细分析了主要的Go微服务框架,并探讨了服务发现与注册和API网关的实现及应用。 关注TechLead,复旦博士,分享云服务领域全维度开发技术。拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,复旦机器

Go 使用原始套接字捕获网卡流量

Go 使用原始套接字捕获网卡流量 Go 捕获网卡流量使用最多的库为 github.com/google/gopacket,需要依赖 libpcap 导致必须开启 CGO 才能够进行编译。 为了减少对环境的依赖可以使用原始套接字捕获网卡流量,然后使用 gopacket 的协议解析功能,这样就省去了解析

Go 如何对多个网络命令空间中的端口进行监听

Go 如何对多个网络命令空间中的端口进行监听 需求为 对多个命名空间内的端口进行监听和代理。 刚开始对 netns 的理解不够深刻,以为必须存在一个新的线程然后调用 setns(2) 切换过去,如果有新的 netns 那么需要再新建一个线程切换过去使用,这样带来的问题就是线程数量和 netns 的数

【建议收藏】Go语言关键知识点总结

容器 数组和切片 在Go语言中,数组和切片是两个基本的数据结构,用于存储和操作一组元素。它们有一些相似之处,但也有许多不同之处。下面我们详细介绍数组和切片的特点、用法以及它们之间的区别。 数组 数组是固定长度的序列,存储相同类型的元素。数组的长度在定义时就固定下来,不能改变。 package mai