GO语言学习心得day04| 青训营笔记

98 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第四天。今天学习了性能分析工具pprof的使用,并进行了性能分析实战,了解到了一些go语言的基本优化方法。

pprof工具的基本使用

 我们在平时运行程序的时候并不不知道我们的程序占用了多少cpu,也不知道使用了多少内存,这不利于我们寻找需要优化的地方,pprof工具就可以帮我们统计方法的消耗,并将数据展示给我们,帮助我们定位需要优化的方法。pprof的具体内容如下:

image.png

功能

我们可以直接使用go get拉取pprof的依赖进行使用,golang的hhtp标准库对ppro进行了一些封装,让我们能够在http服务中使用,它可以记录程序在运行时各种数据,例如:cpu、堆内存、goroutine、锁竞争、阻塞调用,线程数据等,用户可以通过列表、调用图、火焰图、源码、反汇编等视图去展示搜收集到的数据信息。

页面介绍

 Wolfogre提供了练习pprof的项目,并提前埋下了一些炸弹代码供我们使用工具找出,项目入口:

func main() {
   log.SetFlags(log.Lshortfile | log.LstdFlags)
   log.SetOutput(os.Stdout)

   runtime.GOMAXPROCS(1)
   runtime.SetMutexProfileFraction(1)
   runtime.SetBlockProfileRate(1)

   go func() {
      if err := http.ListenAndServe(":6060", nil); err != nil {
         log.Fatal(err)
      }
      os.Exit(0)
   }()

   for {
      for _, v := range animal.AllAnimals {
         v.Live()
      }
      time.Sleep(time.Second)
   }
}

项目启动后我们就可以访问http://localhost:6060/debug/pprof/ 进入监控页面:

image.png

  • allocs:内存分配情况
  • blocks:阻塞操作情况
  • cmdline:程序启动命令及
  • goroutine:当前所有goroutine的堆栈信息
  • heap:堆上内存使用情
  • mutex:锁竞争操作情况
  • profile: CPU占用情况
  • threadcreate:当前所有创建的系统线程的堆栈信息
  • trace:程序运行跟踪数据

pprof实战

cpu的排查

 我采用go tool pprof +采样链接来启动采样,等待一会后会显示这段时间内程序的运行数据,输入top查看cpu使用最高的函数,默认会显示占用最高的前10个函数的信息,一般我们需要优化前几名的函数,使用list 函数名的方式可以定位到具体的一个函数,并展示它每行的占用,在给出的实例项目中,可以定位到eat方法的第24行,它执行了100亿次的空循环,导致cpu占用过高,这样我们就找到了问题所在。

内存的排查

 pprof还提供了 另一种更简便明了的方法,通过-http=:8080参数,可以开启pprof自带的Web UI,性能指标会以网页的形式呈现。输入 go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap 进行采样,完成后会自动打开浏览器,:

image.png 我们可以看到是Steal()方法占用了1g的内存,切换到Source视图下查找Steal方法,我们发现steal函数会向buffer中不断追加内存至到1g,这样我们就找到了内存的问题。

协程排查

 打开pprof的主页,我们可以看到有一百多个协程在运行,这对于一个小程序来说并不正常,将刚才指令的结尾换成gorouting运行后,打开view切换到Flame Graph视图,图中,自顶向下展示了各个调用,表示各个函数调用之间的层级关系,每一行中,条形越长代表消耗的资源占比越多,我们看到wolf.drink()方法创建了绝大部分的协程,通过Source视图找到drink方法,我们看到34行申请了没有意义的10条协程,这样我们就找到了协程的问题。

锁排查

 和之前一样将后缀改成mutex,在graph视图中找到出现问题的函数为wolf.Howl()方法,之后在Source视图搜索该方法,定位到58行发生可锁竞争导致了阻塞,这样我们就找了锁的问题。

阻塞排查

 将结尾改成block之后根据graph视图,找到了问题方法为cat.pee(),在Source中搜索该方法,定位到第39行pee()方法试图读取time.After()方法生成的channel导致阻塞了一秒钟,这样我们就找到了阻塞的问题。