Perf Linux性能事件(性能计数)器 与 Flame Graph

image

from ふぃーる 冬コミ2日目西ふ15

性能分析基础知识

Benchmark

Benchmarks are designed to mimic a particular type of workload on a component or system.

基准旨在模拟组件或系统上特定类型的工作负载。

from wiki

Standard Performance Evaluation Corporation (SPEC)

标准性能评估公司(SPEC)是一个非营利性联盟,负责建立和维护新一代计算系统的标准化基准和性能评估工具。

image

SPECjvm2008就是他们的,其是模拟Java Runtime Environment (JRE)上特定类型的工作负载测试套件

SPECjvm2008 workload names(部分):

startup.helloworld	compiler.compiler	scimark.fft.small
startup.compiler.compiler	compiler.sunflow	scimark.lu.small
startup.compiler.sunflow	compress	scimark.sor.small
...

Workload

"workload" can be broken up into "work+load", referring to the work done with a given load.

“工作量”可以分解为“工作+负载”,指的是在给定负载下完成的工作。

在重量训练方面,“负载”指的是被举起重量的重量(20 公斤的重量比 10 公斤的重量更重),而 “工作”指的是体积,或者说是使用该重量完成的总次数和组数(20 次重复比 10 次重复更费力,但两组 10 次重复的工作量与一组 20 次重复相同)。


  • Benchmark(基准测试):通常指的是一套标准化的测试任务或程序,用来衡量计算机系统(包括硬件和软件)的性能。

  • Workload(工作负载):指的是在真实或模拟环境中,系统实际运行的任务或应用程序的集合。

benchmark往往基于或模拟某类常见的workload,以便测试在标准环境下的系统性能,从而使不同系统或配置之间的性能对比更加公平和准确。

Perf 性能采样和计数原理

首先要清楚perf是一个面向事件的可观察性工具
image

from jyy

perf在中断来临时,获取OS在中断之前所记录的关键性能指标
image

perf工具的数据来源自PMC(Performance Monitoring Counter),PMC 来自 PMU(Performance Monitoring Unit)

  • PMU(性能监控单元) 是处理器中的一个硬件子系统,用于监控和分析 CPU 的运行状况。它负责跟踪各种硬件事件(例如指令执行、缓存命中、分支预测等),并将这些事件的信息存储在 PMC(性能监控计数器)中。

    • PMU 的主要组成部分
      • 事件选择器(Event Selector):负责选择需要监控的事件,比如“指令完成数”或“缓存未命中数”。每个 PMU 通常支持多种事件,通过配置寄存器来指定目标事件。
      • 性能监控计数器(PMC, Performance Monitoring Counter):PMU 的核心部件,用于记录选定事件的发生次数。通常是硬件计数器,以二进制形式累积事件数。
        • 数量有限:由于硬件资源限制,PMC 的数量通常较少(例如 4 或 8 个计数器)。
        • 事件绑定:每个 PMC 可以绑定一个事件,并记录该事件的累计次数。
        • 可复用:通过切换事件绑定,可以在有限的 PMC 中监控更多的事件。
      • 控制寄存器:用于设置和控制 PMU 的工作模式,例如启用/禁用事件监控、设置中断阈值等。
      • 中断支持:PMU 可以在特定事件计数达到阈值时触发中断(PMI Performance Monitoring Interrupt),便于实时性能分析。

perf 事件原理
perf 事件原理图

image

Perf Stat (性能计数)

stat (statistics) 有统计,计数,获取信息等含义

perf stat <command>对程序运行时所发生的性能事件进行统计:
image

from jyy

not supported 是因为jyy老师用的虚拟机上一些功能不支持

Perf Record (性能采样)及其可视化

采样

perf record -o perfData/perf.data -e cycles -g -F 99 ./a
perf report -i perfData/perf.data
  • -o 指定搜集的数据保存地址,需要保证perfData这个目录是存在的
  • -e cycles -e指定采集的事件,cycles表示CPU时钟周期数
  • -g 表示开启调用栈采样,便于分析程序中函数调用关系
  • -F 表示指定采集频率(HZ 每秒采样次数),这里表示每秒采样 99 次
  • perf report中的-i 指定查看采集数据的地址
  • ctrl + c中断采样,即kill -SIGINT $pid_prefrecord,主要不能使用kill -9 $pid_prefrecord进行中断,这样会导致记录数据错误

采集频率的设定会影响性能,不能太大或太小:

Balancing considerations that include sampling overhead, profile size, and application variation, a typical CPU profile might be collected in the following way: stack traces are sampled at a rate of 99 times per second (not 100, to avoid lock-step sampling)

99次是一种常见的使用频率,但是为何频率设置为100会出现所谓的lock-step sampling呢?


Lock-step sampling 指的是当采样事件与系统或程序的周期性行为锁定(lock in)时,采样可能总是在特定的执行状态或代码路径中触发,导致采样结果不能反映程序的全局行为。这会引入以下问题:

  • 偏差(Bias):采样可能总是在某些特定的函数、线程或代码路径上触发,遗漏其他关键路径。
  • 不准确:分析结果可能错误地将某些函数误判为性能瓶颈,或者完全忽视实际的热点。

导致可能有:

  • (a) 系统周期性行为的同步
    现代计算机系统中的许多事件是周期性发生的,例如:

    • CPU 时钟信号的周期(Clock Cycle)。
    • 定时中断的触发频率(例如,操作系统调度器的时间片通常是 10ms,即 100Hz)。
    • 硬件计数器的刷新频率。
      如果采样频率(100Hz)与这些周期性事件的频率相同或成整数倍关系(同步),采样点就会总是落在这些事件的同一个状态上。例如:
    • 每次采样可能总是捕获到某些特定线程正在运行,而忽略了其他线程。
    • 如果某个函数在每个周期开始时被频繁调用,采样可能总是在捕获它,而忽略其他部分。
  • (b) 系统时间片的默认设置
    许多操作系统的任务调度器时间片默认是 10ms(100Hz)。如果采样频率也设为 100Hz,就会导致采样事件总是发生在时间片的开始或结束时。这种情况可能导致:

    • 采样总是在线程切换之前或之后触发,而无法反映线程运行中的真实状态。
    • 某些短时间运行的函数可能被完全忽略,因为它们总是在采样事件之间执行。
  • (c) 代码自身的周期性
    某些代码可能具有周期性行为,例如:

    • 循环内的操作可能具有固定执行时间。
    • 网络或 IO 轮询任务每隔固定时间运行一次。

更加具体的以数学计算表示:

image
image

Perf-Record and Perf-Script

通过Perf record可以得到perf.data二进制文件,我们可以通过perf reportperf script得到人类可读信息。

perf report的信息主要侧重在总结:perf report --no-call-graph --header -i perf.data -F overhead,pid,dso,sym > perf-nogF.report

image

让我来解释下每个参数的含义:

因为这里我利用perf record采集的是两个事件:cycles和instructions。所以在perf record中会记录采集到的某个函数某次消耗cycles和instructions的值。

然后累积总结得到上述信息,在这里以第一行为例子,即进程0所执行的swapper进程,在执行arch_cpu_idle函数时所消耗的cycles为2.39%, 所消耗的instructions为1.89%

[k] 表示这是一个内核空间(kernel space)符号。[.] 表示这是一个用户空间(user space)符号。

Symbol:函数或符号名称,表示性能事件发生的代码位置。

Shared Object:共享对象(动态链接库或内核模块)名称。

如果去除--no-call-graph参数可以更详细地看到在函数执行时各个子调用所消耗的事件占比



perf script 侧重于事件:perf script -i perf.data --hide-call-graph > perf-nog.script

image

从左到右值的含义分别为:

  • command
  • PID
  • CPU
  • 时间戳
  • 事件值
  • 事件
  • 事件发生时的指令地址
  • 事件发生时的函数+偏移量(函数表明在哪个函数发生的,偏移量是从汇编的视角进行理解的,可以通过函数名地址+偏移量得到在哪条汇编指令上发生的事件)
  • 函数所在的文件路径

符号缺失是个非常严重的问题,这会导致函数名未知,而显示出[unknown]。如果想要查看栈信息那么栈帧缺失也是个非常严重的问题。

栈帧和符号

参考巨佬写的博客:The Return of the Frame Pointers

总得来说就是在查看report和火焰图时符号出现了unknow
image

符号是通过栈帧得到的,栈帧是什么?

在 x86-64 架构中,%RBP(Base Pointer) 和 %RIP(Instruction Pointer) 是两个关键寄存器,用于标识栈帧信息和程序的执行位置。同时还有%rsp记录当前栈顶。

CPU 寄存器 %rbp 用作堆栈帧(也称为“帧指针”)的“基指针”。

image

(a)Stack Frame with Base Pointer (x86-64 ABI)

image

(b) Frame Pointer-based Stack Walking -- All from Brendan's site

通过 Stack-Walking(栈遍历),可以获取每个栈帧的函数调用关系、调用地址(程序计数器 PC),以及与函数调用相关的其他元信息。具体过程如下:

  1. 首先由(b)图可知,我们可以通过%rip得到当前正在运行指令的地址,只要能够知道指令的地址,就能够有方法知道这条指令是在那个函数下的,就可以知道函数名等元信息

  2. 然后(a)图可知可以通过当前%rbp的值得到父函数的栈帧地址,即上一栈帧的地址。
    通过对%rbp做位移(即8(%rbp))可以知道父函数的返回后要执行指令的地址,从而得到父函数的元信息。

  3. 利用previous %rbp value进行跳转,跳转到父函数的栈帧上,从而可以得到祖父的栈帧信息和返回后要执行指令的地址信息,从而得到祖父函数的元信息。

  4. 不断如此,可以得到整个函数调用链。

遇到的困难

在上述巨佬写的博客中也描述了遇到的困难:

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 的繁忙程度,所以一般选择暖色调。

最佳实践

参考资料

基于Perf测试SpecJvm2008基准测试

Brendan GreggFollow(上述资料的作者)给出的建议是:
image

为什么我们需要perf-map-agent?

答: 修复Perf所需的Java Symbols

For JlT'd code, Linux perf already looks for an externally provided symbol file: /tmp/perf-PlD.map, andwarns if it doesn't exist

Linux perf 工具 在分析 JIT 编译的代码时,会自动查找一个外部提供的符号文件,该文件的默认路径是 /tmp/perf-PlD.map。如果这个文件不存在,perf 会发出警告,提示符号文件缺失。

JIT 编译后的代码通常没有传统的符号信息,因此需要额外的符号文件来帮助 perf 正确地解码和解析程序的执行数据。

perf-map-agent就能够生成/tmp/perf-PlD.map这个文件,作者使用的方式:

image

jmaps代码

注意这个代码需要运行在root用户下

  • ~/.bashrc 普通用户目录下的环境变量配置文件

  • /etc/profile root用户的环境变量配置文件

  • /etc/sudoers 使用sudo命令时的环境变量配置文件

为什么我们需要Set -XX:+PreserveFramePointer

答:为了修复破损的栈

image

其他

还有如stack depth过低,导致超过此深度的栈信息被忽略的问题,inline导致栈帧丢失的问题,均有说明

执行的命令

 # SPECjvm2008运行基准测试的命令行的一般形式是
 java [<jvm options>] -jar SPECjvm2008.jar [<SPECjvm2008 options>] [<benchmark name> ...]

其中<SPECjvm2008 options>可以通过 props/specjvm.properties and props/specjvm.reporter.properties 这两个文件指定或者手动写在命令中。具体SPECjvm2008参数

通过-pf <string> 来使用properties file.

./run-specjvm.sh startup.helloworld -pf props/specjvm.properties
./run-specjvm.sh startup.helloworld -pf /home/cilinmengye/java_perf/SPECjvm2008/props/specjvm.properties

SPECjvm2008 结果可以存储在哪里?

SPECjvm2008可以从任何目录运行;但是,specjvm.home.dir 必须指定为系统属性并指向 SPECjvm2008 位置(SPECjvm2008.jar 所在位置)。

-Dspecjvm.home.dir=/home/tests/SPECjvm2008指定,如:

java -Dspecjvm.home.dir=/home/tests/SPECjvm2008 -jar /home/tests/SPECjvm2008/SPECjvm2008.jar

SPECjvm2008 结果可以存储在哪里?

-Dspecjvm.result.dir指定,如:

java -Dspecjvm.home.dir=/home/tests/SPECjvm2008 -Dspecjvm.result.dir=/home/results/jvm08-results/ -jar /home/tests/SPECjvm2008/SPECjvm2008.jar

如何禁用报告生成

默认情况下,该工具将生成一个原始文件(xml 格式),其中包含基准测试运行的结果。每次迭代后都会存储结果,以免对测量周期产生影响,而且还会连续进行,以免在基准测试之间甚至迭代之间存储任何额外的数据,这可能会影响基准测试结果。

为了跳过生成所有结果,可以使用命令“-crf false”,以免将任何结果打印到文件中。这意味着不会有原始文件,并且无法后期生成任何 html 报告或文本报告。

Perf + Specjvm2008基准测试

taskset -c 0-3 perf record -o $PERFDATA_PATH -e cycles -g -F 99 -T -- \
        java -XX:+PrintGC -XX:+PreserveFramePointer \
        -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps \
        -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC \
        -Dspecjvm.home.dir=/home/cilinmengye/java_perf/SPECjvm2008 \
        -jar /home/cilinmengye/java_perf/SPECjvm2008/SPECjvm2008.jar \
        -crf false -ikv -ict -i 10 -it 240s -bt 4 scimark.sparse.large >$TMPDATA_NAME 2>&1; ./jmaps
  • taskset -c 0-3 指定CPU

  • perf record -o $PERFDATA_PATH -e cycles -g -F 99 -T perf-record的参数

  • -crf false -ikv -ict -i 10 -it 240s -bt 4 scimark.sparse.large SPECjvm2008的参数

报错

很奇怪,如果-pf /home/cilinmengye/java_perf/SPECjvm2008/props/specjvm.properties不在$SPECJVM2008目录下使用就会报错:Error reading properites file '/home/cilinmengye/java_perf/SPECjvm2008/props/specjvm.properties'
null



WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,
check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.
Samples in kernel functions may not be resolved if a suitable vmlinux
file is not found in the buildid cache or in the vmlinux path.
Samples in kernel modules won't be resolved at all.
If some relocation was applied (e.g. kexec) symbols may be misresolved
even with a suitable vmlinux or kallsyms file.
Couldn't record kernel reference relocation symbol
Symbol resolution may be skewed if relocation was used (e.g. kexec).
Check /proc/kallsyms permission or run as root.

如果perf record没有在sudo或root下运行很有可能会爆如上错误,解决方案:

sudo sh -c " echo 0 > /proc/sys/kernel/kptr_restrict"


Reference

posted @ 2024-11-24 23:14  次林梦叶  阅读(222)  评论(0编辑  收藏  举报