热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Golang程序性能分析

前言程序性能分析我相信是每个程序员都会遇到的问题,比如说一个程序的CPU为什么占用这么高?有没有优化的空间?又比如程序出现了内存泄漏如何排查等等。如果是C++程序会借助于Googlepprof

前言

程序性能分析我相信是每个程序员都会遇到的问题,比如说一个程序的CPU为什么占用这么高?有没有优化的空间?又比如程序出现了内存泄漏如何排查等等。如果是C++程序会借助于Google pprof c++ profile,java程序会依赖于jstack等工具,幸运的是Golang语言本身就集成了性能分析工具pprof包,可以有效的分析程序CPU,MEM,Metux等指标。其中还可以通过第三方图形应用来更加直观的显示每个调用关系和指标占用情况。

 

演示环境

$ uname -a
Darwin 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64

$ go version
go version go1.12.4 darwin/amd64

 

下面我直接用Golang pprof官方提供的示例来演示下CPU和MEM的分析方法,以及集成出图工具后的各种效果。

 

CPU

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")

func main() {
    flag.Parse()
    if *cpuprofile != "" {
        f, err := os.Create(*cpuprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }
    ...

程序定义了一个cpuprofile的文件,用于保存程序的cpu prof信息。StartCPUProfile方法会将信息输出到cpuprofile文件中,程序退出前会调用StopCPUProfile方法,将缓存的信息一并(flush)刷到cpuprofile中。

$ go build havlak1.go
$ ./havlak1 -cpuprofile havlak1.prof 
# of loops: 76000 (including 1 artificial root node)
$ go tool pprof havlak1 havlak1.prof 
File: havlak1
Type: cpu
Time: Jun 18, 2019 at 3:52pm (CST)
Duration: 14.81s, Total samples = 19.20s (129.67%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top 10
Showing nodes accounting for 11090ms, 57.76% of 19200ms total
Dropped 105 nodes (cum <= 96ms)
Showing top 10 nodes out of 99
      flat  flat%   sum%        cum   cum%
    2250ms 11.72% 11.72%     2550ms 13.28%  runtime.mapaccess1_fast64
    1830ms  9.53% 21.25%     4760ms 24.79%  runtime.scanobject
    1470ms  7.66% 28.91%     9250ms 48.18%  main.FindLoops
    1040ms  5.42% 34.32%     1040ms  5.42%  runtime.memmove
     880ms  4.58% 38.91%     3420ms 17.81%  runtime.mallocgc
     840ms  4.38% 43.28%     2660ms 13.85%  main.DFS
     800ms  4.17% 47.45%     1990ms 10.36%  runtime.mapassign_fast64ptr
     790ms  4.11% 51.56%     1020ms  5.31%  runtime.findObject
     600ms  3.12% 54.69%      760ms  3.96%  runtime.heapBitsSetType
     590ms  3.07% 57.76%      600ms  3.12%  runtime.pageIndexOf  

输出top 10 表示的是显示最消耗cpu的10个函数调用。上述每列指标的含义为:

  • flat 采样时,该函数正在运行的次数*采样频率(10ms),记得到估算的函数“运行时间”。不包含等待子函数返回的时间。
  • flat% flat/总采样时间值,例如第一行的数据 2250ms/(19.2*1000)Total samples =11.72%。
  • sum% 前面所有flat%的累加,所以第一行的sum%永远都是等于flat%,第二行21.25%=11.72%+9.53%。
  • cum  采样时,该函数出现在调用堆栈的采样时间。包含等待子函数返回的时间。
  • cum% cum/总采样时间值,例如第一行的数据 2550ms/(19.2*1000)Total samples =13.28%。

上述的文字比例显示说实话没有看出具体的调用关系,有没有更好的图形界面呢,答案是用web命令,前提是已经安装了图像处理软件graphviz,否则会报错:

 

(pprof) web
Failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH
$ brew install graphviz
(pprof) web

输入web命令后会自动唤起浏览器显示调用关系的图片:

 

 但是这样全盘的调用关系老实将也是有点乱,可以通过--nodefraction=0.1把占用低于10%的关系不显示,也可以可以通过web mapaccess1 来查看runtime mapaccess1_fast64的单条调用关系链,会更加的聚焦和清晰。

其实上面的图形还是不够直观,最直观的调用关系和占比图是火焰图,但是火焰图需要依赖第三方火焰图生成工具,不是golang pprof自带的,步骤下:

  • 安装火焰图生成工具 go-torch go get github.com/uber/go-torch
  • 安装火焰图生成脚本 FlameGraph 进入到go-torch的bin路径执行git clone https://github.com/brendangregg/FlameGraph.git,cd FlameGraph && mv * ../
     
u$ go-torch havlak1 havlak1.prof 
INFO[17:38:11] Run pprof command: go tool pprof -raw -seconds 30 havlak1 havlak1.prof
INFO[17:38:11] Writing svg to torch.svg

在执行命令的当前目录下会生成一个torch.svg的文件,用浏览器打开如下:

 

从底往上是一层层调用关系,长方格的长度表示占比。所以说调用关系和占比一目了然。

程序优化了DFS函数后的占用情况:

(pprof) top 5 
Showing nodes accounting for 4410ms, 38.82% of 11360ms total
Dropped 83 nodes (cum <= 56.80ms)
Showing top 5 nodes out of 106
      flat  flat%   sum%        cum   cum%
    1230ms 10.83% 10.83%     3650ms 32.13%  runtime.scanobject
     950ms  8.36% 19.19%     5430ms 47.80%  main.FindLoops
     840ms  7.39% 26.58%      840ms  7.39%  runtime.memmove
     730ms  6.43% 33.01%     2680ms 23.59%  runtime.mallocgc
     660ms  5.81% 38.82%      810ms  7.13%  runtime.findObject
(pprof)

可以看出现在函数的主要消耗在内存的申请和回收上面(runtime.mallocgc)。为了找出为什么gc回收这么频繁,我们必须先找出究竟是哪里在频繁的申请内存。所以引入了下面的MEM章节。

MEM

var memprofile = flag.String("memprofile", "", "write memory profile to this file")
...

    FindHavlakLoops(cfgraph, lsgraph)
    if *memprofile != "" {
        f, err := os.Create(*memprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.WriteHeapProfile(f)
        f.Close()
        return
    }

分析运行的结果:

$ go tool pprof havlak3 havlak3.prof 
File: havlak3
Type: inuse_space
Time: Jun 18, 2019 at 5:05pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top5
Showing nodes accounting for 34.51MB, 100% of 34.51MB total
Showing top 5 nodes out of 13
      flat  flat%   sum%        cum   cum%
      18MB 52.17% 52.17%       18MB 52.17%  main.NewBasicBlock
    9.72MB 28.16% 80.32%     9.72MB 28.16%  main.FindLoops
       3MB  8.69% 89.02%        3MB  8.69%  main.(*BasicBlock).AddOutEdge
    2.29MB  6.64% 95.65%    20.29MB 58.80%  main.(*CFG).CreateNode
    1.50MB  4.35%   100%     1.50MB  4.35%  main.(*BasicBlock).AddInEdge
(pprof) 

显示具体的函数内部内存占用情况:

(pprof) list CreateNode
Total: 34.51MB
ROUTINE ======================== main.(*CFG).CreateNode in /Users/haiweilu/saas/src/awesomeProject/test/havlak3.go
    2.29MB    20.29MB (flat, cum) 58.80% of Total
         .          .     94:    }
         .          .     95:    if node != len(cfg.Blocks) {
         .          .     96:        println("oops", node, len(cfg.Blocks))
         .          .     97:        panic("wtf")
         .          .     98:    }
         .       18MB     99:    bblock := NewBasicBlock(node)
    2.29MB     2.29MB    100:    cfg.Blocks = append(cfg.Blocks, bblock)
         .          .    101:
         .          .    102:    if len(cfg.Blocks) == 1 {
         .          .    103:        cfg.Start = bblock
         .          .    104:    }
         .          .    105:
(pprof) list FindLoops
Total: 34.51MB
ROUTINE ======================== main.FindLoops in /Users/haiweilu/saas/src/awesomeProject/test/havlak3.go
    9.72MB     9.72MB (flat, cum) 28.16% of Total
         .          .    263:        return
         .          .    264:    }
         .          .    265:
         .          .    266:    size := cfgraph.NumNodes()
         .          .    267:
    1.97MB     1.97MB    268:    nonBackPreds := make([]map[int]bool, size)
    5.77MB     5.77MB    269:    backPreds := make([][]int, size)
         .          .    270:
    1.97MB     1.97MB    271:    number := make([]int, size)
         .          .    272:    header := make([]int, size, size)
         .          .    273:    types := make([]int, size, size)
         .          .    274:    last := make([]int, size, size)
         .          .    275:    nodes := make([]*UnionFindNode, size, size)
         .          .    276:
(pprof) 

可以看到程序又大量的临时变量的创建和销毁,导致了golang的gc机制频繁触发,从而导致CPU飙高。有gc机制的编程语言并不意味着可以忽略内存分配问题,这个问题的简单解决是保存一个全局的成员变量,避免内存的频繁申请和释放。

总结

本文讲解了go pprof包分析golang 程序 CPU和MEM的用法,其中包含文字界面,简单的web图形界面和火焰图,其实pprof还可以分析程序的其他指标,更多的技能解锁等待大家自行发现。 

参考

https://blog.golang.org/profiling-go-programs

 


推荐阅读
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了三种方法来实现在Win7系统中显示桌面的快捷方式,包括使用任务栏快速启动栏、运行命令和自己创建快捷方式的方法。具体操作步骤详细说明,并提供了保存图标的路径,方便以后使用。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 怀疑是每次都在新建文件,具体代码如下 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
author-avatar
涵涵幸福2010_759
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有