一. 前言
编写高性能相关的程序经常需要涉及到 CPU 的调优,我们需要先找出程序占用 CPU 时间较长的热点代码,然后再优化这一部分的代码。Linux 系统提供了一个 perf 命令可以记录程序在运行时单个函数级别的调用统计信息,再配合 Flame Graph 将统计信息转化为比较直观的方式(火焰图),帮助我们定位程序 CPU 过高的问题所在。
如果发现自己系统上没有安装 perf,可以参考这篇博客安装(适用于 Ubuntu,其他系统的可以自行百度一下),Flame Graph 只需要将其 clone 到本地就可以使用了。
二. perf+Flame Graph的使用
假设当前有一个 ./server 程序正在运行,进程号 PID=20000。
1. 首先使用 perf record 命令记录进程的 CPU 使用情况,命令如下,如果是一个不断运行的进程,我们可以在记录一段时间后使用 Ctrl+C 结束 perf record,此时在当前目录下会生成一个 perf.data 的文件,该文件记录着进程的函数调用情况以及各个函数所占的 CPU 时间比例。
命令:sudo perf record -e cpu-clock -g -p 20000
说明:perf record 命令是用来记录统计信息的,-e cpu-clock 代表需要记录统计的指标是 cpu 的使用情况,-g 代表记录函数的调用关系,-p 后面跟进程的 PID。
2. 使用 perf script 工具对 perf.data 进行解析
命令:sudo perf script -i perf.data &> perf.unfold
3. 使用 Flame Graph 工具将 perf.unfold 中的符号折叠
命令:sudo FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
4. 使用 Flame Graph 工具将 perf.folded 生成 svg 火焰图
命令:sudo FlameGraph/flamegraph.pl perf.folded > perf.svg
三. 实例演示
假设当前有一串代码如下所示,我们需要分析这串代码中哪里比较耗 CPU。首先使用 g++ flamegraph_test.cpp -g -o flamegraph_test,编译生成可执行代码。
#include <iostream>
using namespace std;
int Add(int a,int b)
{
return a+b;
}
int Sub(int a,int b)
{
return a-b;
}
int main()
{
while(1)
{
cout << "Hello,world" << endl;
int num1 = Add(3,4);
int num2 = Sub(4,3);
}
return 0;
}
运行程序后我们发现这串代码居然占了 66.1% 的 CPU,于是我们根据 perf+Flame Graph 记录程序的 CPU 使用情况,最终生成一个 svg 火焰图。
关于如何看懂上面这个火焰图,建议参考阮一峰老师的这篇博客。简单来说就是宽度越大,占用的 CPU 时间越多,而纵轴从下往上是函数调用的关系,所以如果要调查性能问题,我们应该调查 queue_work_on 函数。Workqueue(WQ) 机制是 Linux 内核中最常用的异步处理机制,queue_work_on 函数实际上是 work 放进 workqueue 中,我们可以往下再观察哪个函数调用了此函数,发现是 pty_write,该函数是往终端打印的系统函数,因此我们知道造成 CPU 使用率过高的主要因素是 cout << "Hello,world" << endl; 这条语句。