前言
这篇博客来聊一聊如何使用perf进行性能优化。
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
目录
- 前言
- 1. perf介绍
- 2. 安装perf
- 3. perf初探
- 3.1 perf list使用
- 3.2 perf record使用
- 3.3 perf stat使用
- 3.4 perf top使用
- 3.5 perf diff使用
- 3.6 火焰图
- 4. 实际操作
- 4.1 生成执行文件并放入设备运行
- 4.2 生成perf.data文件
- 4.3 生成火焰图
- 5. 问题
- 参考资料
1. perf介绍
perf
可以在CPU Usage
增高的节点上找到具体引起CPU
增高的函数,之后就可以有针对的聚焦那个函数做分析。
举例来说,使用 Perf 可以计算每个时钟周期内的指令数,称为 IPC,IPC 偏低表明代码没有很好地利用 CPU。Perf 还可以对程序进行函数级别的采样,从而了解程序的性能瓶颈究竟在哪里等等。Perf 还可以替代 strace,可以添加动态内核 probe 点,还可以做 benchmark 衡量调度器的好坏。
2. 安装perf
详见博主的另一篇博客,里面记录了ubuntu下和嵌入式系统重perf
的安装过程,按照流程来是可以正常安装成功的:perf的安装与迁移
3. perf初探
3.1 perf list使用
perf list
可以列出所有的采样事件。
perf list
至此,我们了解到event
都有哪些类型,即每行最后[]
中的内容,最主要的是三个event,如下所示:
Hardware event: 由PMU(电源管理单元,具有许多与普通计算机相似的组间,包括固件和软件,存储器,CPU,输入/输出功能)产生,如L1缓存命中等,当想了解程序对硬件特性的使用情况时,就可以对这些事件采样。
Software event: 由内核产生的事件,如进程切换等
Tracepoints event: 由内核静态跟踪点所触发的事件,这些tracepoint用来判断程序运行期间内核的行为细节,如slab分配器的分配次数等
3.2 perf record使用
perf record
命令可以用来采集数据,并把数据写入数据文件中,随后可以通过perf report
命令对数据文件进行分析。
具体有什么用呢?举例来说明。比如说我们已经断定目标程序计算量较大,也许是因为有些代码写的不够精简。那么面对长长的代码文件,究竟哪几行代码需要进一步修改呢?这便需要使用 perf record 记录单个函数级别的统计信息,并使用 perf report 来显示统计结果(perf record表示记录到文件,perf top直接会显示到界面)。
perf record
常用选项如下所示:
-e: 选择一个事件,可以是硬件事件也可以是软件事件
-a: 全系统范围的数据采集
-p: 指定一个进程的ID来采集特定进程的数据
-o: 指定要写入采集数据的数据文件
-g: 使能函数调用图功能
-C: 只采集某个CPU的数据
同时还可以使用grep
来使用程序名监控程序
perf record -e event -g -p grep your_program
perf record
常用选项如下所示:
-i: 导入的数据文件名称,默认为perf.data
-g: 生成的函数调用关系图
--short: 分类统计信息,如PID、COMM、CPU
在使用上,可以先用perf record
指令保存信息到perf.data
中,在用perf report
指令输出record
的结果。
perf record -a -p -F 99 -- sleep 10
perf report
最后的结果如图所示:
3.3 perf stat使用
当我们接到一个性能优化任务时,最好采用自顶向下的策略。先整体看看该程序运行时各种统计事件的汇总数据,再针对某些方向深入处理细节。
有些程序运行的慢是因为计算量太大,起多数时间在使用CPU进行计算,这类程序叫CPU-Bound
型;而有些程序运行的慢是因为过多的I/O,这时其CPU利用率应该不高,这类程序叫I/O-Bound
型。这二者之间的调优是不同的。
perf stat
选项,可以在终端上执行命令时收集性能统计信息
该指令的选项如下所示:
-a: 显示所有CPU上的统计信息
-c: 显示指定CPU上的统计信息
-e: 指定要显示的事件
-p: 指定要显示的进程ID
我们在本地跑一个进程,这个程序是一直向终端打印hello world,我们使用ps aux
查看进程
ps aux
可以看到我们刚刚运行的hello
的线程号是86
。
然后我们利用perf stat可以查看进程的相应信息,输入后还需要输入ctrl+c杀死进程
perf stat -p 86
ctrl+c(键盘上按这两个键)
task-clock(msec)
是指程序运行期间占用了xx个的任务时钟周期,单位为毫秒,该值高就说明程序的多数时间花费在CPU计算上而非IOcontext-switches
是指程序运行期间发生了xx次上下文切换,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换应该是避免的(进程与进程间,内核态与用户态之间)cpu-migrations
是指程序运行期间发生了xx次CPU迁移,即用户程序原本在一个CPU上运行,后来迁移到另一个CPUpage-faults
是指程序发生了xx次页错误cycles
:消耗的处理器时钟数,一条机器指令可能需要多个cyclesInstructions
:机器指令数目,表示执行了多少条指令,IPC平均为每个CPU时钟周期执行了多少条指令branches
: 遇到的分支指令数branch-misses
: 预测错误的分支指令数- 其他可以控制的譬如分治预测、cache命中等
3.4 perf top使用
该指令用于实时显示当前系统的性能统计信息。该命令主要用来观察整个系统当前的状态,比如可以通过查看该命令的输出来查看当前系统最耗时的内核函数或某个用户进程
perf top
该指令可以看出进程中哪个函数消耗资源比较多
第一列显示给定函数正使用的CPU百分比
第二列显示使用函数的程序或库的名称
第三列显示函数名称或符号,内核空间中执行的功能由[k]
标识,用户空间中执行的功能则用[.]
标识
此外 perf top
还有常见的选项,如下所示:
-e: 指定要分析的性能事件
-p: 仅分析目标进程
-k: 指定带符号表信息的内核映像路径
-K: 不显示内核或者内核模块的符号
-U: 不显示属于用户态程序的符号
-g:显示函数调用关系图
3.5 perf diff使用
当多次perf record
后,当前路径会生成一个perf.data
和perf.data.old
文件,分别表示本次和上次的record
记录,如果要看二者之间的区别,对比优化结果,那么可以使用perf diff
指令
sudo perf diff perf.data perf.data.old
3.6 火焰图
需要去github下载分析脚本
git clone https://github.com/brendangregg/FlameGraph.git
下载下来后,先使用perf record
生成perf.data
文件,例如输入如下指令
sudo perf record -e cpu-clock -g -p 10465
之后会根据perf.data
进行解析,生成perf.unfold
文件
perf script -i perf.data &> perf.unfold
将perf.unfold
中的符号进行折叠,生成perf.folded
FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
最后生成svg图
FlameGraph/flamegraph.pl perf.folded > perf.svg
也可以用参数-width
和-height
来指定每一条的宽度和高度
FlameGraph/flamegraph.pl perf.folded > perf.svg -width 1000 -height 10
生成的火焰图,宽度越大表示CPU耗时越多
4. 实际操作
4.1 生成执行文件并放入设备运行
接下来进行一段代码实战,来感受一下实际过程中如何查看代码性能并定位。
首先在Ubuntu
上写测试代码test.cpp
,如下所示,即多加几个循环,foo()
中调用bar()
,do_main()
调用foo()
,最后在while(1)
中调用do_main()
。由代码可以看出来,foo()循环时间要长一些,因此理应在该函数的时间最长,其次是bar()
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
using namespace std;
void bar(){
for(int i=0;i< 4000;i++)
{
}
}
void foo(){
for(int i=0;i< 5700;i++)
{
}
bar();
}
void do_main() {
foo();
}
int main(int argc,char** argv){
while(1)
{
do_main();
// cout << "hello" << endl;
}
}
然后使用编译链编译该cpp文件
g++ Desktop/test.cpp -o Desktop/testLinux
然后我们在后台运行起这个文件
.Desktop/testLinux &
4.2 生成perf.data文件
首先查看进程号
ps -xu | grep testLinux
可以使用perf top 实时查看一下
sudo perf top -e cpu-clock -p 4502
可以看到foo()
函数占用的时间最多,其次是bar()
,和我们最初的想法一致。按下ctrl+c
即可退出返回至控制台
下面来生成perf.data
文件
perf record -e cpu-clock -g -p 4502
使用report
查看
perf report
4.3 生成火焰图
解析perf.data文件
perf script -i perf.data &> FlamGraph/result/perf.unfold
将unfold文件的字符进行压缩
FlameGraph/stackcollapse-perf.pl FlamGraph/result/perf.unfold &> FlamGraph/result/perf.folded
生成火焰图
FlameGraph/flamegraph.pl FlamGraph/result/perf.folded > FlamGraph/result/perf.svg
结果如下所示:
火焰图上面是bar(),然后其组成了foo()的全部,如果我们要优化代码的话可以从这两个函数下手。
5. 问题
-
问题:输入
perf record
指令后且没有指定sleep时间,进程一直在阻塞状态
解决:按下ctrl+c,即可生成perf.data文件 -
问题:执行
FlameGraph/flamegraph.pl
和FlameGraph/stackcollapse-perf.pl
的过程中出问题
解决:使用chmod 777
将这两个文件的权限改一下 -
问题:最后生成的svg中,文字显示
failed to open perf.data: Permission denied
解决:将
perf.data
文件用chmod 777
修改一下权限
参考资料
[1] perf性能分析工具使用分享