您现在的位置是:首页 >技术杂谈 >GO 性能分析网站首页技术杂谈

GO 性能分析

daliucheng 2024-08-26 00:01:02
简介GO 性能分析

GO 性能分析

简介

​ go提供了内存分析工具,pprof利用它可以看cpu和内存的情况。

包含下面的几种类型:

  • cpu
  • 内存
  • 阻塞

pprof分为大体分为两个部分

  1. 数据采集
  2. 数据分析

数据采集有两种方式:

  1. 官方

    官方提供了两种方式

    • runtime/pprof

      这是用来给没http服务来使用

      https://pkg.go.dev/runtime/pprof

    • net/http/pprof

      给http服务来使用的,提供了路由来方便访问。

      https://pkg.go.dev/net/http/pprof

  2. 三方库

    • profile

      https://github.com/pkg/profile

数据分析:

  1. go tool pprof

    官方文档:https://github.com/google/pprof

使用

数据采集

cpu

启动 CPU 分析时,运行时(runtime) 将每隔 10ms 中断一次,记录协程(goroutines) 的堆栈信息。一个函数在性能分析数据中出现的次数越多,说明执行该函数的代码路径(code path)花费的时间占总运行时间的比重越大。

  1. 代码

    package main
    
    import (
    	"fmt"
    	_ "net/http/pprof"
    	"os"
    	"runtime/pprof"
    )
    
    func main() {
    	// 手动创建pprof文件
    	f, err := os.Create("cpu_test.pprof")
    	if err != nil {
    		return 
    	}
    	// 启动
    	if err := pprof.StartCPUProfile(f); err != nil {
    		return 
    	}
    	//defer 停止
    	defer pprof.StopCPUProfile()
    	
    	var a []int
    
    	for i := 0; i < 100000; i++ {
    		a = append(a, i)
    	}
    	res := twnSum(a, len(a)*3)
    	fmt.Printf("%v",res)
    }
    func twnSum(data []int,sum int) []int  {
    	for i, itemA := range data {
    		for i2, itemB := range data {
    			if i == i2{
    				continue
    			}
    			if itemA + itemB == sum{
    				return []int{i,i2}
    			}
    		}
    	}
    	return nil
    }
    

    这种方式是我用原生库实现的,开启方式在原生库的文档里面有,分为下面三步:

    • 创建pprof文件
    • 启动cpu profile
    • 在defer中停止cpu profile

    运行此代码

     go run .main.go
    

    在项目的根目录下面会有cpu_test.pprof,到此数据采集一级成功,分析在后面章节会介绍。

    三方库也是对基本代码的再次包装

profile库实现方式
// 主体代码都一样,采集的代码不一样
	defer profile.Start().Stop()

就这一行,开启了cpu profile,看看它做了什么事情。

在这里插入图片描述

  1. 返回了一个接口,接口里面有stop方法
  2. 通过options(入参)来配置prof
  3. 选择模式
  4. 这代码是上面用pprof包做的事情

它支持的模式有:

在这里插入图片描述

从代码可以看到,在调用start方法的时候可以通过入参来配置Profile,它的入参是func的可变切片。我们可以自定义,同时它提供了一些方法便于我们操作:

在这里插入图片描述

使用方式如下:

	defer profile.Start(
		profile.CPUProfile,
		profile.ProfilePath("D:/GolandProjects/base_go/pprof"),
		).Stop() // 指定采集类型为CPU,并且指定pprof文件的路径

内存

内存性能分析(Memory profiling) 记录堆内存分配时的堆栈信息,忽略栈内存分配信息。

它也有采样比,默认是每分配512KB的时候采样一次,每次都需要采样的时候设置为1,不需要的时候设置为0。

这里我们直接用profile包来做

//主体代码都一样,只是profile变化
defer profile.Start(profile.MemProfile).Stop()

在log里面会显示pprof文件的目录。

数据分析

有两种方式

  1. web界面
  2. 交互界面

文档:https://github.com/google/pprof/blob/main/doc/README.md

这个文档解释了命令行和图形化中字段的含义。在下面的例子中我会对部分做解释说明。

web界面

需要安装Graphviz

官网:https://graphviz.org/

启动方式:

go tool pprof -http=:9989 .cpu_test.pprof
// 格式如下
go tool pprof -http=主机:端口 pprof文件

访问localhost:9989界面如下:

在这里插入图片描述

说明:web界面也是将命令行的显示图形化,两者是一样,在这里做详细的解释之后,命令行就不多做解释了。

界面元素解释如下:

官网:https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph

view界面

文档:https://github.com/google/pprof/blob/main/doc/README.md#view

在这里插入图片描述

top

在这里插入图片描述

Graph

在这里插入图片描述

Flame

在这里插入图片描述

命令行交互界面

go tool pprof  C:UsersliuchenAppDataLocalTempprofile4178991121cpu.pprof

其余的命令行操作看官方文档

要解释一下Flat和Cum

文档上是这么说的:

在这里插入图片描述

我理解是:

pprof的数据是通过采样获取的,并且数据要有垂直结构,每个采样点都有自己的代价和它所有的调用方法包括一块的代价

flat:采样点自己的代价。

cum:采样点调用方法一块的代价。

http服务

数据采集

原生go http服务方式

前提:启动http服务,用go原生的(非必须,之后可以自己做自定义)

package main

import (
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
)

func handler(w http.ResponseWriter, r * http.Request) {
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
	http.HandleFunc("/", handler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

导入net/http/pprof

import _ "net/http/pprof"

访问 http://localhost:8080/debug/pprof/看到下面的页面就说明ok了。我们就可以获取对应的pprof报告了。

在这里插入图片描述

使用方式和源码分析

看到这种导包方式,就知道在init方法里面干了一些事情。

在这里插入图片描述

可以看到,在init里面它自己注册了几个路由,每一个路由对应页面上的操作。只要看一下这些方法就知道可以传什么参数了,本质上来说,这里数据采集的方法也是和上面的一样,只不过提供了http接口,我们还是以cpu为例来看一下。
在这里插入图片描述

可以传递收集时间,默认30s 。这里收集的方法和一开始的例子是一样的。

和三方http库结合

按照上面的思路,和三方http结合就是将init方法中注册的路由注册到三方库的http中就好。这里以echo为例子

echo: https://echo.labstack.com/guide/

代码:

package main

import (
	"net/http"
	"net/http/pprof"

	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})
	e.GET("/debug/pprof",echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/allocs", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/block", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/goroutine", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/heap", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/mutex", echo.WrapHandler(http.HandlerFunc(pprof.Index)))
	e.GET("/debug/pprof/cmdline", echo.WrapHandler(http.HandlerFunc(pprof.Cmdline)))
	e.GET("/debug/pprof/profile", echo.WrapHandler(http.HandlerFunc(pprof.Profile)))
	e.GET("/debug/pprof/symbol", echo.WrapHandler(http.HandlerFunc(pprof.Symbol)))
	e.GET("/debug/pprof/trace", echo.WrapHandler(http.HandlerFunc(pprof.Trace)))
	e.Logger.Fatal(e.Start(":1323"))
}

在这里插入图片描述

echo中注册路由的方法是:

在这里插入图片描述

WrapHandler做了一层适配。

数据分析

  1. 查看heap profile

    go tool pprof http://localhost:8080/debug/pprof/heap
    
  2. 查看30s cpu profile

    go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
    
  3. 查看阻塞的goroutine

    go tool pprof http://localhost:6060/debug/pprof/block
    
  4. 查看mutexes

    go tool pprof http://localhost:6060/debug/pprof/mutex
    
  5. 查看trace

    curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
    go tool trace trace.out
    
  6. 查看pprof

    curl -o profile.out http://localhost:8080/debug/pprof/profile?debug=1
    go tool pprof   profile.out
    

引用下面文章:
https://geektutu.com/post/hpg-pprof.html

到这里结束了。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。