C++分析程序各模块耗时-perf火焰图
Reference:
perf: Linux profiling with performance counters(带有性能计数器的Linux分析)
1. 简介
perf 是一个非常实用且深入的性能分析工具,适用于从底层硬件交互到上层应用程序逻辑的全方位性能剖析。
perf 工具的设计目的是为了帮助开发者和系统管理员分析应用程序以及内核本身的性能,寻找潜在的性能瓶颈,并据此进行针对性的优化。
perf 主要特性包括以下几个方面:
- 函数级和指令级热点分析:perf 可以通过采样分析,精确到函数级别或甚至指令级别来发现CPU占用率高的热点代码段;
- 性能计数器:利用Linux内核的perf_events子系统,它能够监控和记录各种硬件事件,如CPU cycles、指令执行、缓存未命中的次数、分支预测失败等,还可以监控软件事件,如系统调用、页错误等;
- CPU性能监测:监控各个进程或线程的CPU使用率,识别哪些函数或代码路径是CPU消耗大户(和第一条好像没区别);
- 缓存行为分析:分析内存访问模式,包括L1、L2、L3缓存的命中和未命中的情况,有助于优化内存访问策略;
- 系统调用和事件跟踪:记录和分析程序运行过程中的系统调用,以及相关的事件流;
- 调用图生成:创建函数调用图,可视化地展示函数间的调用关系,方便理解程序执行流程及其开销;
- 动态跟踪:支持动态跟踪技术,可以实时捕获和分析程序运行时的行为;
- 跨进程和跨线程分析:不仅能分析单个进程的性能,还能处理多进程间的数据关联,提供全面的系统级视图;
- 兼容性:由于其直接集成在内核中,perf 具备良好的硬件兼容性,能够充分利用不同架构CPU的性能监控单元(PMU)。
2. 工作原理
perf 工作原理的核心在于对 Linux 内核性能事件的支持和利用。以下是 perf 工具的基本工作原理概述:
硬件事件(Hardware Events)
:perf 能够利用CPU内部的Performance Monitoring Units (PMUs)
。PMUs是一组硬件寄存器,能够追踪和记录特定的微架构事件,如指令执行次数、缓存未命中、分支预测失败等。通过编程这些寄存器,perf可以设置特定事件的阈值,在达到一定次数后触发中断,收集此时的上下文信息,如CPU寄存器状态、PC指针(指向当前正在执行的指令地址)、进程ID、线程ID等。软件事件(Software Events)
:对于软件级别的事件,perf 利用内核中的计数器和 tracepoints。内核计数器可以追踪系统级事件,如上下文切换、页错误、任务调度等。Tracepoints 是内核中预定义的静态探针点,允许在特定位置插入额外的追踪代码,以便在关键内核操作发生时收集信息。采样(Sampling)
:perf 工具主要采用采样法进行性能分析。它可以根据设置的事件类型和频率定期或者在满足条件时触发采样,每次采样都会记录下当时CPU正在执行的指令或函数信息。通过大量的采样数据,可以得出热点函数或代码段,从而找出可能导致性能瓶颈的部分。记录与报告(Recording and Reporting)
:perf 可以实时或事后分析记录的数据。它可以创建统计数据报告,展示CPU使用率、上下文切换、内存访问统计等信息,还可以生成调用图(火焰图)以直观地显示函数调用层级和耗时情况。跟踪(Tracing)
:除了采样之外,perf 还提供了更复杂的跟踪能力,允许持续记录系统的活动,包括系统调用、函数入口退出、irq处理等,这对于分析系统行为和优化系统响应时间非常有用。
总之,perf 集成了多种性能分析手段,通过对硬件和软件事件的监控,结合采样和跟踪技术,实现了对Linux系统和应用程序的深度性能剖析。
2. 安装
sudo apt install linux-tools-common
// 下面步骤根据 Linux 内核来。比如查看 uname -a 得到内核版本,根据相应版本修改下面指令
sudo apt install linux-tools-5.15.0-101-generic
- 查看 perf 版本
perf --version
3. perf 相关命令
最常用的perf命令及其功能理解如下:
annotate 读取perf.data(由perf record生成)并结合源代码展示详细的性能分析结果,包括CPU执行热点、函数调用栈等信息。
archive 使用perf.data文件中找到的带构建标识符的对象文件创建归档文件,便于后续对这些对象文件进行调试或者分析
bench 通用基准测试套件框架,允许用户定义和运行多种基准测试场景,用于评估系统在不同条件下的性能表现。
buildid-cache 管理perf用来关联二进制文件与其符号表信息的构建ID缓存,可以添加、删除或查看缓存内容。
buildid-list 列出perf.data文件中的构建标识符
c2c 共享数据C2C/HITM分析器:针对共享数据缓存一致性(Cache-to-Cache)和高速缓存命中(Hit in Translation)进行分析的工具,帮助诊断多核心间的缓存交互问题。
config 读取和设置perf配置文件中的变量,用于个性化perf的行为或指定默认参数。
daemon 在后台运行记录会话,适用于长期监控系统的性能情况。
data 提供一系列与perf.data文件相关联的操作,如检查文件内容、转换格式等。
diff 比较两个或多个perf.data文件,分析并展示它们之间的性能差异,常用于对比不同条件下系统的性能变化。
evlist 列出perf.data文件中的事件名称,这些事件可能包括硬件性能计数器事件、软件事件、tracepoint事件等。
ftrace 内核ftrace功能的简单包装器,允许实时追踪和分析内核函数调用路径。
inject 过滤器,用于向事件流中添加附加信息,增强事件的数据量和丰富度,便于更加深入地分析性能问题
iostat 显示I/O性能指标,如块设备读写速率、I/O操作延时等
kallsyms 在运行中的内核中搜索符号
kmem 用于跟踪/测量内核内存属性的工具,如分配、释放、碎片率等
kvm 用于跟踪/测量KVM虚拟机操作系统性能的工具
list 列出所有符号事件类型
lock 分析系统中锁的获取和释放行为,包括锁竞争、等待时间等,有助于发现潜在的并发瓶颈
mem 对内存访问模式进行分析,包括页错误、缓存未命中的次数、内存带宽使用等。
record 执行命令并将其性能概要记录到perf.data中
report 读取perf.data(由perf record创建)并显示概要
sched 用于跟踪/测量调度器属性(延迟)的工具,从而优化进程调度策略。
script 读取perf.data(由perf record创建)并显示跟踪输出
stat 执行命令并收集性能计数器统计信息
test 运行内置的一系列sanity测试,确保perf工具自身正确性和稳定性。
timechart 工具用于可视化工作负载期间的系统整体行为
top 系统性能分析工具,类似于Linux的top命令,但专注于性能分析,显示正在运行进程的实时性能统计数据。
version 显示perf二进制文件的版本信息
probe 定义新的动态跟踪点,使得perf能够追踪和记录自定义的内核函数或模块行为。
trace 类似strace的工具,用于跟踪系统调用和信号
3. 测试示例
#include <stdio.h>
#include <stdlib.h>
void long_test() {
int i, j;
for (i = 0; i < 1000000; i++) j = i;
}
void foo2() {
int i;
for (i = 0; i < 10; i++) long_test();
}
void foo1() {
int i;
for (i = 0; i < 100; i++) long_test();
}
int main(void) {
foo1();
foo2();
}
-
编译成二进制文件
g++ -o test test.cpp
-
使用 perf 对系统 CPU 事件做采样
采样60s,会生成一个perf.data文件(采样时间可自行设定):#方式一:对一个正在运行的进程,进行采样
perf record -p PID[这里换成要检测的进程ID] -g sleep 60
#方式二:全新运行一个二进制文件main,进行采样
sudo perf record -F 99 -g ./test sleep 60 -
安装火焰图
利用这个开源工具可以将报告生成可视化的svg图片,更容易查看对应的CPU开销时间和调用栈深度:git clone --depth 1 https://github.com/brendangregg/FlameGraph.git
#安装perl
yum install -y perl -
生成火焰图
生成火焰图的脚本,对二进制文件main进行10秒的采样,然后生成火焰图。
非root用户需要加sudo。sudo perf record -g ./test sleep 10
sudo perf script -i perf.data &> perf.unfold
#火焰图的两个功能
./FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
./FlameGraph/flamegraph.pl perf.folded > perf.svg
我自己的:
sudo perf record -g ./build_pc/dead_reckoning sleep 10
perf script -i perf.data &> perf.unfold
/home/yj/sda/third_party/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
/home/yj/sda/third_party/FlameGraph/flamegraph.pl perf.folded > perf.svg
上面的方式中,[unknown] 出现过多,可考虑将 -g(默认为 fp)
修改为 --call-graph
。可参考 使用 perf 进行性能分析时如何获取准确的调用栈
- | 优点 | 缺点 |
---|---|---|
fp | None | 默认 fp 被优化掉了根本不可用。 |
lbr | 高效准确 | 需要较新的 Intel CPU 才有此功能;2. 能记录的调用栈深度有限。 |
dwarf | 准确 | 1. 开销相对较大;2. 需要编译时附加了调试信息。 |
sudo perf record --call-graph dwarf ./build_pc/dead_reckoning sleep 10
//<-生成折叠后的调用栈,该命令从名为 perf.data 的 perf 数据文件中提取详细的性能事件样本信息,并以可读的文本格式输出
//<-这一步生成的 perf.unfold 文件包含了未折叠的调用栈信息,即每次采样的函数调用链
//<-perf.unfold 是上一步生成的包含详细调用栈信息的文件
sudo perf script -i perf.data &> perf.unfold
//<-生成火焰图
//<- ./stackcollapse-perf.pl 是 FlameGraph 工具集中的一部分,用于处理 perf 生成的未折叠调用栈数据。
//<-经过 stackcollapse-perf.pl 处理后的文件内容是对原始调用栈进行了折叠统计,
//<-即将同一调用路径下的事件次数累加,形成一种更紧凑的格式,便于生成火焰图。
/home/yj/sda/third_party/FlameGraph/stackcollapse-perf.pl perf.unfold &> perf.folded
//<- 最后生成 svg 图
//<- ./flamegraph.pl 是 FlameGraph 工具集中用于生成火焰图的脚本
//<- 将火焰图生成的输出重定向到了名为 perf.svg 的SVG矢量图形文件中。
/home/yj/sda/third_party/FlameGraph/flamegraph.pl perf.folded > perf.svg
SVG是一种可缩放的矢量图形格式,生成的火焰图可以直接在浏览器中查看,形象地展示了各个函数在CPU执行过程中的调用关系及其占用时间比例,有助于定位性能瓶颈。
4. 从火焰图可以获得的信息
- 调用栈从下往上,下层为父类,上层为子类。
- 点击父类缩小,点击子类放大。
- 只关注自己实现的函数名,忽略标准库中的函数。
- 总结一下,火焰图的宽度用于比较不同函数或代码路径的性能,而高度用于显示函数调用堆栈的深度。
5. 生成火焰图常见问题
- Stack count is low (1). Did something go wrong?
-> sudo perf script 时没加 root 权限。