Golang 性能分析

原文链接:https://www.hezebin.com/article/66a8c83c4379b36dec11a197

什么时候做性能分析?

性能分析是一种评估软件系统性能的方法,它涉及测量和评估程序或系统的运行效率,包括但不限于处理速度、资源使用效率、响应时间等。

当你的系统存在性能瓶颈,想要优化代码、提高系统的整体性能和响应速度的时候,就需要做性能分析了。

所以其实只有在高负载时,性能分析才有意义!

性能分析的阶段

性能分析可以在软件系统开发和维护的多个阶段进行,如在【需求分析和技术设计阶段】就可以通过良好的架构设计、数据结构和算法选择来满足性能需求。对于 Golang 程序开发人员,针对语言生态和特性,本文主要通过聊聊【开发阶段】运行数据采集、【测试阶段】性能指标测试验证和【生产环境监控阶段】的问题排查来介绍如何在 Go 程序中做性能分析。

数据采集

Golang 是一个对性能特别看重的语言,官方自带了性能分析相关的工具和库 pprof(Performance Profiling),对于我们常见的微服务HTTP应用,只需要简单的 2 行代码即可实现对数据的实时采集:

package main

import "net/http"
import _ "net/http/pprof"

func main() {
  // 启动一个HTTP服务器,用于访问pprof相关数据
	http.ListenAndServe(":6060", nil)
  // 你的程序逻辑
}

可以看到只需要引入net/http/pprof包,然后启动一个使用默认 handler 的DefaultServeMux的 HTTP 服务。查看源码可以看到 net/http/pprof包的初始化函数init向默认 DefaultServeMux 中注册了一些以/debug/pprof开头的路由:

浏览器访问:http://locahost:6060/debug/pprof

打开的页面下除了有上述init中显式注册的路由外,还有一些其他子页面路由,这些路由通过Index动态的去处理:


然后在不同的路由下,去找到不同的 runtime/pprof.Profile 做对应的数据采集:

上述各个采集项的描述如下:

类型描述
allocs内存分配情况的采样信息
blocks阻塞操作情况的采样信息
cmdline显示程序启动命令及参数
goroutine当前所有协程的堆栈信息
heap堆上内存使用情况的采样信息
mutex锁争用情况的采样信息
profileCPU 占用情况的采样信息
threadcreate系统线程创建情况的采样信息
trace程序运行跟踪信息

进一步查看如对 CPU 指标数据的采集源码:

其本质还是通过调用 runtime/pprof 包下的方法,采集一段时间内的数据。所以对于非 Web 类型的应用程序,如脚本程序运行一段时间就结束了,要对其实现性能分线前的数据采集,示例如下:

import (
    "log"
    "os"
    "path/filepath"
    "runtime/pprof"
)
// 进行CPU监控
func CreateProfileFile() {
    dir, err := os.Getwd()
    if err != nil {
        log.Fatalln("get current directory failed.", err)
    }
    
    fileName := filepath.Join(dir, "pprof", "profile_file", "profile_file")
    f, _ := os.Create(fileName)
    // start to record CPU profile and write to file `f`
    _ = pprof.StartCPUProfile(f)
    // stop to record CPU profile
    defer pprof.StopCPUProfile()
    // TODO do something
}

如果使用自定义的 Mux ,则需要像上述init一样手动注册一些路由规则。如使用gin框架,可以直接使用github.com/gin-contrib/pprof包的pprof.Register函数,其本质也是手动注册:

// Register the standard HandlerFuncs from the net/http/pprof package with
// the provided gin.Engine. prefixOptions is a optional. If not prefixOptions,
// the default path prefix is used, otherwise first prefixOptions will be path prefix.
func Register(r *gin.Engine, prefixOptions ...string) {
	RouteRegister(&(r.RouterGroup), prefixOptions...)
}

// RouteRegister the standard HandlerFuncs from the net/http/pprof package with
// the provided gin.GrouterGroup. prefixOptions is a optional. If not prefixOptions,
// the default path prefix is used, otherwise first prefixOptions will be path prefix.
func RouteRegister(rg *gin.RouterGroup, prefixOptions ...string) {
	prefix := getPrefix(prefixOptions...)

	prefixRouter := rg.Group(prefix)
	{
		prefixRouter.GET("/", gin.WrapF(pprof.Index))
		prefixRouter.GET("/cmdline", gin.WrapF(pprof.Cmdline))
		prefixRouter.GET("/profile", gin.WrapF(pprof.Profile))
		prefixRouter.POST("/symbol", gin.WrapF(pprof.Symbol))
		prefixRouter.GET("/symbol", gin.WrapF(pprof.Symbol))
		prefixRouter.GET("/trace", gin.WrapF(pprof.Trace))
		prefixRouter.GET("/allocs", gin.WrapH(pprof.Handler("allocs")))
		prefixRouter.GET("/block", gin.WrapH(pprof.Handler("block")))
		prefixRouter.GET("/goroutine", gin.WrapH(pprof.Handler("goroutine")))
		prefixRouter.GET("/heap", gin.WrapH(pprof.Handler("heap")))
		prefixRouter.GET("/mutex", gin.WrapH(pprof.Handler("mutex")))
		prefixRouter.GET("/threadcreate", gin.WrapH(pprof.Handler("threadcreate")))
	}
}

性能分析

!!! example 示例项目可参考
https://github.com/ihezebin/go-template-ddd
!!!

上述采集到的数据,不论是 Web 程序浏览器访问还是非 Web 程序的数据文件其内容可读性都非常差:

这样的数据格式和数据量很难直观的分析出程序的性能表现情况。对此,Golang 的 pprof 工具自带了 go tool pprof 命令对采集的数据进行更生动和友好的展示!

终端分析

下述命令执行后将进入一个交互式终端,完整命令:

go tool pprof [binary] file
  • binary:正在执行的二进制可执行程序,可选。
  • file:pprof监控生成的文件。可以是具体的文件如profile.pprof,也可以是web站点的地址,如http://localhost:6060/debug/pprof/profile。

在终端中输入help可查看可用的命令,如top命令将排序列出资源使用较高的调用:

又如通过list 代码命令(支持正则),list emitLocation 可以查看函数和代码情况:

比较好用的一个命令是web,该命令可以在Web Browser上图形化显示当前的资源监控内容,不过需要事先安装 Graphviz(tips:依赖的东西有点多,下载安装时间可能有点久)。

web命令的实际行为是生成一个 .svg文件,并调用系统里设置的默认打开 .svg 的程序打开它。如果系统里打开 .svg 的默认程序并不是浏览器(比如代码编辑器),需要设置一下默认使用浏览器打开 .svg 文件。然后浏览器自动打开:

该图更好的展示了调用层级关系和资源占用情况,方便我们更高效的定位到高资源消耗或占用的位置。

如果需要其他命令,可以在pprof交互式终端里通过help查看其他命令的使用方法。例如:

  • svg:生成svg图。
  • pdf:生成pdf文件,显示svg图。
  • png:生成png图片,显示svg图。

… 更多其他的命令和选项,请自行学习验证。

除了在交互终端中导出 svg 外,还可以不进入终端导出:

go tool pprof -svg allocs > allocs.svg

可视化分析

最新版的 Golang pprof (v1.10+)已经支持动态的 Web 浏览方式查看所有类型的资源监控图:

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

该命令会启动一个Web服务器:http://localhost:6061。一般会自动弹出打开浏览器页面并显示分析结果,默认显示的是 Graph。但是可以从第一行的菜单中切换 View,选择 Flame Graph 即可显示火焰图:

性能测试

go test 是 Go 语言官方的测试工具,用于运行包中的测试函数。

单元测试

Go 语言的测试函数遵循一定的命名规则,通常是以 Test 开头,后面跟着一个大写字母,然后是测试用例的描述。例如 `TestExample。测试函数不需要参数,也不需要返回值。

go test 的基本用法如下:

go test [包名] [标志]

例如,如果你有一个名为 example 的包,你可以使用以下命令来运行该包中的所有测试:

go test example

如下是一个单元测试示例,假设你有一个简单的 math 包,里面有一个 Add 函数,你想要为这个函数写一个单元测试:

// math.go
package math

func Add(a, b int) int {
    return a + b
}

单元测试文件通常命名为 *_test.go,例如 math_test.go

// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
    if Add(1, 2) != 3 {
        t.Errorf("Add(1, 2) = %d; expected 3", Add(1, 2))
    }
}

运行单元测试:

go test

基准测试

-benchgo test 的一个选项参数,用于基准测试。基准测试是一种性能测试,用于测量代码段的执行时间。使用 -bench 时,你可以指定要运行的基准测试函数的正则表达式模式。

-bench 的基本用法如下:

go test -bench [模式] [包名或文件]

例如,如果你想要运行所有以 Benchmark 开头的基准测试函数,你可以使用以下命令:

go test -bench .

这里的 . 表示当前目录下的所有 Go 文件。

如下是一个基准测试示例,如果你想要测量 Add 函数的性能,可以写一个基准测试:

// math_test.go
package math

import "testing"

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

基准测试函数接收一个 *testing.B 类型的参数,b.N 是一个非常大的数字,表示循环的次数。基准测试会运行多次循环,以减少测量误差。

运行基准测试:

go test -bench BenchmarkAdd

请注意,基准测试的输出会告诉你每次操作的纳秒数,以及总共运行了多少次。

test 集成 pprof

go test 命令有两个参数和 pprof 相关,它们分别指定生成的 CPU 和 Memory Profiling 保存的文件:

  • cpuprofile:cpu profiling 数据要保存的文件地址
  • memprofile:memory profiling 数据要报文的文件地址

比如下面执行测试的同时,也会执行 CPU Profiling,并把结果保存在 cpu.prof 文件中:

go test -bench . -cpuprofile=cpu.prof

执行结束之后,就会生成 main.testcpu.prof 文件。要想使用 go tool pprof,需要指定的二进制文件就是 main.test

通常情况下,Profiling 一般和性能测试一起使用,这个原因在前文也提到过,只有应用在负载高的情况下 Profiling 才有意义。

参考

  • https://www.jianshu.com/p/6175798c03b4
  • https://eddycjy.com/posts/go/tools/2018-09-15-go-tool-pprof/#%E6%80%BB%E7%BB%93
  • https://blog.csdn.net/cbmljs/article/details/86642669
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值