Perf 性能采样和计数原理
首先要清楚perf是一个面向事件的可观察性工具
perf在中断来临时,获取OS在中断之前所记录的关键性能指标
Perf Stat (性能计数)
stat (statistics) 有统计,计数,获取信息等含义
perf stat <command>
对程序运行时所发生的性能事件进行统计:
not supported 是因为jyy老师用的虚拟机上一些功能不支持
Perf Record (性能采样)及其可视化
采样
perf record -o perfData/perf.data -e cycles -g -F 99 ./a
perf report -i perfData/perf.data
-o
指定搜集的数据保存地址-e cycles
-e
指定采集的事件,cycles
表示CPU时钟周期数-g
表示开启调用栈采样,便于分析程序中函数调用关系-F
表示指定采集频率(HZ 每秒采样次数),这里表示每秒采样 99 次perf report中的-i
指定查看采集数据的地址
采集频率的设定会影响性能,不能太大或太小:
99次是一种常见的使用频率,但是为何频率设置为100会出现所谓的lock-step sampling呢?
Lock-step sampling 指的是当采样事件与系统或程序的周期性行为锁定(lock in)时,采样可能总是在特定的执行状态或代码路径中触发,导致采样结果不能反映程序的全局行为。这会引入以下问题:
- 偏差(Bias):采样可能总是在某些特定的函数、线程或代码路径上触发,遗漏其他关键路径。
- 不准确:分析结果可能错误地将某些函数误判为性能瓶颈,或者完全忽视实际的热点。
导致可能有:
-
(a) 系统周期性行为的同步
现代计算机系统中的许多事件是周期性发生的,例如:- CPU 时钟信号的周期(Clock Cycle)。
- 定时中断的触发频率(例如,操作系统调度器的时间片通常是 10ms,即 100Hz)。
- 硬件计数器的刷新频率。
如果采样频率(100Hz)与这些周期性事件的频率相同或成整数倍关系(同步),采样点就会总是落在这些事件的同一个状态上。例如: - 每次采样可能总是捕获到某些特定线程正在运行,而忽略了其他线程。
- 如果某个函数在每个周期开始时被频繁调用,采样可能总是在捕获它,而忽略其他部分。
-
(b) 系统时间片的默认设置
许多操作系统的任务调度器时间片默认是 10ms(100Hz)。如果采样频率也设为 100Hz,就会导致采样事件总是发生在时间片的开始或结束时。这种情况可能导致:- 采样总是在线程切换之前或之后触发,而无法反映线程运行中的真实状态。
- 某些短时间运行的函数可能被完全忽略,因为它们总是在采样事件之间执行。
-
(c) 代码自身的周期性
某些代码可能具有周期性行为,例如:- 循环内的操作可能具有固定执行时间。
- 网络或 IO 轮询任务每隔固定时间运行一次。
更加具体的以数学计算表示:
栈帧和符号
参考巨佬写的博客:The Return of the Frame Pointers
总得来说就是在查看report和火焰图时符号出现了unknow
符号是通过栈帧得到的,栈帧是什么?
在 x86-64 架构中,%RBP(Base Pointer) 和 %RIP(Instruction Pointer) 是两个关键寄存器,用于标识栈帧信息和程序的执行位置。同时还有%rsp记录当前栈顶。
CPU 寄存器 %rbp 用作堆栈帧(也称为“帧指针”)的“基指针”。
通过 Stack-Walking(栈遍历),可以获取每个栈帧的函数调用关系、调用地址(程序计数器 PC),以及与函数调用相关的其他元信息。具体过程如下:
-
首先由(b)图可知,我们可以通过%rip得到当前正在运行指令的地址,只要能够知道指令的地址,就能够有方法知道这条指令是在那个函数下的,就可以知道函数名等元信息
-
然后(a)图可知可以通过当前%rbp的值得到父函数的栈帧地址,即上一栈帧的地址。
通过对%rbp做位移(即8(%rbp))可以知道父函数的返回后要执行指令的地址,从而得到父函数的元信息。 -
利用
previous %rbp value
进行跳转,跳转到父函数的栈帧上,从而可以得到祖父的栈帧信息和返回后要执行指令的地址信息,从而得到祖父函数的元信息。 -
不断如此,可以得到整个函数调用链。
遇到的困难
在上述巨佬写的博客中也描述了遇到的困难:
the flame graph looks ok at first glance. But there are 15% of samples on the left, above "[unknown]", that are in the wrong place and missing frames.
The problem is that this system has a default libc that has been compiled without frame pointers, so any stack walking stops at the libc layer, producing a partial stack that's missing the application frames.
libc 是 C 标准库 (Standard C Library) 的实现,是所有 C 程序运行时的基础库,提供了许多核心功能。
系统中默认的 libc(标准 C 库)没有启用 frame pointers(帧指针) 的编译选项,导致在使用工具进行 栈回溯(stack walking) 时只能获取部分调用栈信息。这种情况下,调用栈在到达 libc 层时中断,无法追溯到应用程序层的调用栈信息。
有些编译器(如 GCC 和 Clang)or 系统默认会启用优化选项(如 -fomit-frame-pointer),省略帧指针 以释放寄存器资源,提高性能。
可视化
y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。 注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。
最佳实践
参考资料
- The Flame Graph This visualization of software execution is a new necessity for performance profiling and debugging -- 详细介绍火焰图在性能分析上的运用
- Java Performance Analysis on Linux with Flame Graphs -- 用火焰图分析Java性能