在确保代码运行正确无误之后,我们往往都需要进行性能优化,不过受限于阿姆达尔定律,性能优化需要针对热点代码进行。热点代码可以通过分析算法复杂度获得,也可以通过运行性能分析工具获得。在此我们给大家介绍一组工具callgrind+gprof2dot+dot,可以非常方便地分析代码瓶颈。
1.Callgrind
Valgrind是一款常用的代码分析工具,在大多数情况下我们都会用它来帮我们查找内存泄露和内存读写错误,但实际上,它的功能远不止如此。利用它的callgrind工具我们还可以对代码各部分的运行时间进行分析。使用方法如下:
valgrind --tool=callgrind --callgrind-out-file=callgrind.out ./your_command
2.gprof2dot
上述命令会生成一个callgrind.out的性能分析文件,不过该文件不容易直接读懂,我们可以利用gprof2dot.py脚本将其转换成可以可视化的dot格式,进而利用dot命令生成图片。该脚本的使用方法很简单,基本命令如下:
python gprof2dot.py -f callgrind -n 0.5 -e 0.5 -s callgrind.out >perf.dot
简单描述就是它会分析callgrind的输出文件,并将输出结果重定向到dot格式的文件中。具体的用法我们把官网上的描述翻译一下。
2.1 命令行选项
选项 | 含义 |
---|---|
-o FILE, --output=FILE | 指定输出文件,默认是标准输出(也即屏幕),还可以通过重定向来实现该选项 |
-n PERCENTAGE, --node-thres=PERCENTAGE | 运行时间占比小于PERCENTAGE的节点(函数调用)不展示(默认阈值是0.5) |
-e PERCENTAGE, --edge-thres=PERCENTAGE | 作用和上面类似,对边进行剪枝 |
-f FORMAT, --format=FORMAT | 性能分析日志的来源,支持的来源有:axe, callgrind, hprof, json, oprofile, perf, prof, pstats, sleepy, sysprof, xperf。该脚本支持多种性能分析工具,默认是prof,我们采用的是callgrind分析工具。 |
–total=TOTALMETHOD | 采用哪种方法统计总时间:callratios 或者 callstacks(只对perf分析工具有作用),默认是callratios 。 |
-c THEME, --colormap=THEME | 颜色主题:color, pink, gray, bw,默认是color,效果也最好。 |
-s, --strip | 不显示函数参数、模版参数、常量修饰符等对展示无影响的符号,启用该选项会让生成的图片更直观易懂,一般启用。 |
-w, --wrap | 当函数名过长的时候自动换行,启用会让生成的图片更美观 |
–show-samples | 展示function samples? |
-z ROOT, --root=ROOT | 只展示ROOT函数及其子孙调用关系,忽略其他调用,当函数调用关系复杂只分析某一部分函数的调用关系时非常有用。 |
-l LEAF, --leaf=LEAF | 只展示某个函数及其父亲调用关系,忽略其他调用。 |
–skew=THEME_SKEW | 调整节点颜色的区分度。值<1区分度更明显,否则更不明显。建议用默认值,或者<1的值。 |
2.2 节点格式
输出图片中的每个节点展示一个函数调用的详细信息,具体格式如下:
function name |
---|
total time % ( self time % ) |
total calls |
其中,total time表示该函数在总运行时间中的占比,self time表示该函数除去可显示的子函数时间之后剩余的时间占比,total calls表示该函数被调用的次数(包括递归调用)。每条边展示的是子节点的时间占比和调用次数。
2.3 FAQ
- 如何生成一个完整的调用图?
默认情况下,gprof2dot脚本会忽略时间占比非常小的函数,导致生成的调用图不全。如果想统计所有函数的调用关系和时间占比,我们可以将-n和-e的阈值设置为0。 - 节点的函数名太长,怎么让它变窄?
默认情况下,函数名会包括作用域,参数和模版参数等,导致函数名特别长。我们可以通过-s选项去掉这些信息,也可以通过-w命令将其换行。 - 脚本生成的图片为空,或者所有节点都是相同的颜色,时间占比加和不一致?
当程序执行时间很短时,脚本无法获取足够的信息来确定各函数的执行时间导致图片为空或者时间占比加和不一致。所以使用gprof2dot的前提是程序要运行比较长的时间,这样才能获取比较准确的函数调用图。 - 编译时应该传递什么编译选项?
为了生成比较准确的性能分析结果,必须要传递下面两个选项:
- -g:产生debug信息
- -fno-omit-frame-pointer:使用frame pointer(frame pointer usage is disabled by default in some architectures like x86_64 and for some optimization levels; it is impossible to walk the call stack without it)
如果使用的性能分析工具是gprof,我们还需要添加-pg选项。
如果希望分析结果和release版很接近,我们还需要添加如下两个选项: - -O2:初步优化
- -DNDEBUG:禁掉标准库中的debug代码(例如assert宏)
在很多情况下,启用代码优化之后会导致很多函数被展开,使得性能分析工具不准确,我们可以添加选项避免函数内联展开: - -fno-inline-functions:不要将函数展开到父函数中,这样函数计时都会归到父函数中;
- -fno-inline-functions-called-once:和上面类似
- -fno-optimize-sibling-calls:do not optimize sibling and tail recursive calls (otherwise tail calls may be attributed to the parent function)
在开启比较高等级优化的情况下,有些函数即使没有加inline关键字,编译器也会自动给你展开成内联函数,可以通过下面两个选项关闭自动内联: - -fno-default-inline:do not make member functions inline by default merely because they are defined inside the class scope
- -fno-inline:do not pay attention to the inline keyword Note however that with these last options the timings of functions called many times will be distorted due to the function call overhead. This is particularly true for typical C++ code which expects that these optimizations to be done for decent performance.
3.dot
dot命令是graphviz的一部分,通过编写一些类似脚本的语言,可以容易地生成流程图,其输出格式包括PostScript,PDF,SVG,PNG,含注解的文本等等,在fedora系统上可以通过yum install graphviz安装。在此我们不关注dot脚本的格式及其编写,只关心将gprof2dot生成的dot文件转换成图片进行可视化。
格式:dot -T -o <infile.dot>
输入文件是<infile.dot>,生成的格式由指定,生成的文件是。针对前面生成的perf.dot文件,我们可以通过命令dot -Tpng perf.odt -o perf.png来生成png格式的图片。另外我们也可以利用管道将上述命令组合在一起:
python gprof2dot.py -f callgrind -n 0.5 -e 0.5 -s callgrind.out | dot -Tpng -o perf.png
下面是一张用上述三个命令组合生成的一个性能分析图,从该图我们可以很清晰地看出每个函数的时间占比: