性能分析 | 青训营笔记

132 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第 3 天

一、本课重点内容

  • 编程规范
  • 性能调优

二、详细知识点介绍

性能调优实战

  • 简介
  • 性能分析工具 pprof
  • 性能调优案例

简介

性能调优原则:

  • 要依靠数据而不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

pprof 工具

当进行性能优化的时候,大家肯定想要知道应用在什么地方消耗的较大的资源(CPU、Memory等等)导致了性能瓶颈,

而pprof是用于可视化和分析性能数据的强大工具

golang在语言层面集成了profile采样工具,在程序运行过程中可以获取cpu、heap、block、traces等执行信息,这些会涉及到 runtime/pprofnet/http/pprofruntime/trace等package

功能简介

image-20230117214622360

获取数据
工具应用

主要使用runtime/pprof库,将数据写入文件中,适用于很小的程序

执行完后,会发现cpuprofile、memoryprofile文件,里面包含cpu、内存的画像。 runtime/pprof 直接支持这两种画像。

package main
​
import (
    "fmt"
    "os"
    "runtime/pprof"
)
​
func main() {
    //CPU Profile
    f, err := os.Create("./cpuprofile")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()
​
    //Memory Profile
    fm, err := os.Create("./memoryprofile")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer fm.Close()
    pprof.WriteHeapProfile(fm)
​
    for i := 0; i < 100; i++ {
        fmt.Println("hello")
    }
}
Web服务

我们可以通过引入net/http/pprof包,并在主函数中添加以下代码即可进行性能分析

使用默认 ServerMux,在代码中直接使用 http.ListenAndServe("0.0.0.0:8000",nil)(当第二个参数为nil时,需要匿名引用)

package main
​
import (
   "fmt"
   "net/http"
   _ "net/http/pprof"
   "strings"
)
​
func hello(w http.ResponseWriter, r *http.Request) {
   r.ParseForm()       //解析参数,默认是不会解析的
   fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
   fmt.Println("path", r.URL.Path)
   fmt.Println("scheme", r.URL.Scheme)
   fmt.Println(r.Form["param"])
   for k, v := range r.Form {
      fmt.Println("key:", k)
      fmt.Println("val:", strings.Join(v, ""))
   }
   fmt.Fprintf(w, "Hello 掘金!")
}
​
func main() {
   http.HandleFunc("/", hello)       //设置访问的路由
   err := http.ListenAndServe(":9090", nil) //设置监听的端口
   if err != nil {
      fmt.Printf("ListenAndServe: %s", err)
   }
}

执行上述代码后,访问 http://127.0.0.1:9090/?param=111 即可看见返回内容"Hello 掘金!"

此时访问 http://127.0.0.1:9090/debug/pprof/ 可以看见pprof工具为我们配置的调试页面

Gin框架

在gin中引入pprof工具非常简单!

只需要两步即可轻松使用pprof!

  • 引入gin-contrib/pprof
  • 调用 pprof.Register(router)
grpc服务器

上述的几种应用都能通过链接或本地访问,如果像grpc这种类型的服务该如何使用pprof进行性能分析?

pprof做CPU分析原理是按照一定的频率采集程序CPU(包括寄存器)的使用情况,所以我们可以

  1. gRPC服务启动时,异步启动一个监听其他端口的HTTP服务,通过这个HTTP服务间接获取gRPC服务的分析数据
  2. 因为gin使用默认的ServerMux(服务复用器),所以只要匿名导入net/http/pprof包,这个HTTP的复用器默认就会注册pprof相关的路由
小结

通过上述几种类型,看出应用服务核心在于将pprof的路由注册到服务中,并能够提供访问。

数据分析

到这里我们通过pprof获取到了性能相关的数据,可以存储在文件中或是在浏览器中显示,但我们该如何分析这些数据呢?

Golang在 1.11 版本后在自带的工具集 go tool中集成了pprof工具来分析由pprof库生成的数据文件。

使用go tool pprof分析数据,也有两种方式

  • go tool pprof [url(http://host:port/debug/pprof/profile)] 进入命令行交互模式

  • 文件方式

    • go tool pprof [文件名] 进入命令行交互模式
    • go tool pprof -http=:9091 [文件名] 进入web分析页面

进入命令行交互模式后,可以使用help查看所有子命令,使用help <cmd|option>查看子命令使用方法。

使用profile可以获取很多重要信息,cpu profiling、memory profiling使用也是最频繁的。分析的时候,需要先获取到数据,通过web发现耗时的函数,然后通过list找到具体位置。

其它的数据的分析和CPU、Memory基本一致。下面列一下所有的数据类型:

  1. http://localhost:8082/debug/pprof/ :获取概况信息,即图一的信息
  2. go tool pprof http://localhost:8082/debug/pprof/allocs : 分析内存分配
  3. go tool pprof http://localhost:8082/debug/pprof/block : 分析堆栈跟踪导致阻塞的同步原语
  4. go tool pprof http://localhost:8082/debug/pprof/cmdline : 分析命令行调用的程序,web下调用报错
  5. go tool pprof http://localhost:8082/debug/pprof/goroutine : 分析当前 goroutine 的堆栈信息
  6. go tool pprof http://localhost:8082/debug/pprof/heap : 分析当前活动对象内存分配
  7. go tool pprof http://localhost:8082/debug/pprof/mutex : 分析堆栈跟踪竞争状态互斥锁的持有者
  8. go tool pprof http://localhost:8082/debug/pprof/profile : 分析一定持续时间内CPU的使用情况
  9. go tool pprof http://localhost:8082/debug/pprof/threadcreate : 分析堆栈跟踪系统新线程的创建
  10. go tool pprof http://localhost:8082/debug/pprof/trace : 分析追踪当前程序的执行状况

三、 课后个人总结

希望大家在生产环境中不会用到pprof,正常情况下,大部分问题都不需要用到pprof即可解决。使用pprof是有一定成本的,必须要想办法先能获取到数据。

如果真的遇到线上问题必须使用pprof,建议先想好要分析哪类数据。

pprof对程序的性能优化还是很有利的,获取数据后,可以快速定位到耗时较多的位置进行优化,而且也支持只打印和某个函数相关的命令,很人性化。