perf —— linux cpu性能分析工具

1,298 阅读6分钟

生成CPU时间片火焰图

# 1. 安装 perf
sudo apt-get install linux-tools-common linux-tools-generic linux-tools-`uname -r`
# 2. 下载火焰图工具
## 建议切换到 commit d9fcc272b6a08c3e3e5b7919040f0ab5f8952d65 或之后
## 此工程应下载到pc机等非嵌入式环境(开发板)上
https://github.com/brendangregg/FlameGraph
# 3. 查看系统限制
sysctl kernel.kptr_restrict 
# 4. 设置系统限制
sudo sysctl -w kernel.kptr_restrict=0
sudo sysctl -w kernel.perf_event_paranoid=-1
# 5. 采集信息
sudo su
## 如果你需要用非系统目录下的动态库,那一定执行 export LD_LIBRARY_PATH=xxx
## 应测试使用 fp, dwarf, lbr 三种栈调用解析器,找到解析结果正确的那一种。具体原理见下文“解析调用栈”
perf record -e cpu-cycles --call-graph=<fp/dwarf/lbr> <xxx / -p pid>
# 6. 生成中间文件
perf script -i perf.data &> perf.unfold
# 7. 使用火焰图工具工程中的脚本
## 在进行开发板or嵌入式平台测试时,此步骤应回到pc机上进行
export PATH=<FlameGraph工程目录>:$PATH
stackcollapse-perf.pl perf.unfold &> perf.folded
flamegraph.pl perf.folded > perf.svg
# 8. 使用浏览器打开即可

# 6 -> 7 一步完成的命令
perf script  | stackcollapse-perf.pl | flamegraph.pl  > out.svg

生成CPU时间片函数分布表

perf report -i perf.data
perf report -g --sort cpu -i perf.data

调研程序性能瓶颈 (包括Cache Miss)

当初次调研一个软件的性能时,应先确定程序性能状态。

即,CPU Bound、IO bound的情况。

perf stat -d -B -e cache-references,cache-misses,cycles:uk,instructions,branches,faults,migrations,branch-misses,page-faults <xxx>

以上统计信息的解释

  • Task-clock-msecs:CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO。
  • Context-switches:进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。
  • CPU-migrations:表示进程 t1 运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行。
  • Cycles:处理器时钟,一条机器指令可能需要多个 cycles,
  • Instructions: 机器指令数目。
  • IPC:是 Instructions/Cycles 的比值,该值越大越好,说明程序充分利用了处理器的特性。
  • Cache-references: cache 命中的次数
  • Cache-misses: cache 失效的次数
  • branch-misses: 分支预测失败的次数和耗时

Perf指导

被测软件的编译参数

有些编译优化项会忽略frame pointer,所以编译软件时必须指定 -fno-omit-frame-pointer ,才能跟踪完整的stack trace。

在编译软件时打开符号表的支持(gcc -g),开启annotate的支持(gcc -ggdb)。

这些选项的具体作用见下一小节”解析调用栈“

# 在CMakeLists.txt中加入此句话
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -g -ggdb ")

解析调用栈方式、

加入-fno-omit-frame-pointer -g -ggdb都是为了增加可被解析的栈痕迹。当加入了对应编译选项后,perf record便可以通过 --call-graph=<fp/dwarf/lbr>选项确定追踪栈信息的方式。具体对应关系如下

具体该使用哪个选项,应以实际测试结果为准。

fp 就是 Frame Pointer,即 x86 中的 EBP 寄存器,fp 指向当前栈帧栈底地址,此地址保存着上一栈帧的 EBP 值,具体可参考此文章的介绍,根据 fp 就可以逐级回溯调用栈。然而这一特性是会被优化掉的,而且这还是 GCC 的默认行为,在不手动指定 -fno-omit-frame-pointer 时默认都会进行此优化,此时 EBP 被当作一般的通用寄存器使用,以此为依据进行栈回溯显然是错误的。不过尝试指定 -fno-omit-frame-pointer 后依然没法获取到正确的调用栈,根据 GCC 手册的说明,指定了此选项后也并不保证所有函数调用都会使用 fp。从实际使用上来看,Orin上的perf即很好支持 fp 选项。

dwarf 是一种调试文件格式,GCC 编译时附加的 -g 参数生成的就是 dwarf 格式的调试信息,其中包括了栈回溯所需的全部信息,使用 libunwind 即可展开这些信息。dwarf 的进一步介绍可参考 “关于DWARF”,值得一提的是,GDB 进行栈回溯时使用的正是 dwarf 调试信息。

lbr 即 Last Branch Records,是较新的 Intel CPU 中提供的一组硬件寄存器,其作用是记录之前若干次分支跳转的地址,主要目的就是用来支持 perf 这类性能分析工具,其详细说明可参考 “An introduction to last branch records” & “Advanced usage of last branch records”。此方法是性能与准确性最高的手段,然而它存在一个很大的局限性,由于硬件 Ring Buffer 寄存器的大小是有限的,lbr 能记录的栈深度也是有限的,具体值取决于特定 CPU 实现,一般就是 32 层,若超过此限制会得到错误的调用栈。

触发记录的方式

perf记录信息需要触发。

  • 周期性检测 // TODO
  • 事件触发,见下文。

Perf能够观察的事件类型包括:

PERF_TYPE_HARDWARE :内核能够提供的“通用的”硬件事件PERF_TYPE_SOFTWARE:内核提供的软件定义的事件(即使没有硬件的支持)PERF_TYPE_TRACEPOINT:由Ftrace 架构提供,支持采样功能PERF_TYPE_HW_CACHE:这些是硬件事件,但是由于其复杂性,因而需要特殊的编码
PERF_TYPE_RAW:只有某些CPU支持的特定事件

其中,PERF_TYPE_HARDWARE类事件包括:

cpu-cycles:某段时间内的CPU cycle数;

instructions:某段时间内的CPU所执行的指令数;

cache misses:cache miss次数;

branch misses:分支预测错误次数;

等等

而PERF_TYPE_SOFTWARE包含的事件包括:

cpu-clock:某段时间内的cpu时钟数;

page faults:页错误次数;

context switches:上下文交换次数;

等等。

其中CPU cycles事件和cpu-clock事件因比较常用,我们说一下它们的区别:

cpu-clock可以用来表示程序执行经过的真实时间,而无论CPU处于什么状态(Pn(n非0)或者是C状态);

关于CPU的状态见: vstinner.github.io/intel-cpus.…

而CPU cycles则用来表示执行程序指令花费的时钟周期数,如果CPU处于Pn(n非0)或者是C状态,则cycles的产生速度会减慢。

也即,如果你想查看哪些代码消耗的真实时间多,则可以使用cpu-clock事件;而如果你想查看哪些代码消耗的时钟周期多,则可以使用CPU cycles事件。

获取受支持事件的列表:perf list

Perf report 阅读

perf report

# Events: 1K cycles
#
# Overhead          Command                   Shared Object  Symbol
# ........  ...............  ..............................  .....................................
#
    28.15%      firefox-bin  libxul.so                       [.] 0xd10b45
     4.45%          swapper  [kernel.kallsyms]               [k] mwait_idle_with_hints
     4.26%          swapper  [kernel.kallsyms]               [k] read_hpet
     2.13%      firefox-bin  firefox-bin                     [.] 0x1e3d
     1.40%  unity-panel-ser  libglib-2.0.so.0.2800.6         [.] 0x886f1
     [...]

“开销”列表示在相应函数中收集的总样本的百分比。第二列报告了收集样本的过程。在每线程/每进程模式下,这始终是受监视命令的名称。但在 CPU 范围模式下,命令可能会有所不同。第三列显示样本来源的 ELF 图像的名称。如果程序是动态链接的,则可能会显示共享库的名称。当样本来自内核时,则使用伪 ELF 映像名称 [kernel.kallsyms]。第四列指示采样的特权级别,即程序在中断时正在运行时:

  • [.]:用户级别
  • [k]:内核级别
  • [g]:客户机内核级别(虚拟化)
  • [u]: 来宾 os 用户空间
  • [H]:虚拟机管理程序

查看系统perf支持

$gunzip -c /proc/config.gz | grep PERF
$cat /boot/config-`uname -r` |grep CONFIG_KALLSYMS // 查看系统是否支持内核代码的符号表

CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_KALLSYMS_EXTRA_PASS=y

其他内核编译选项

# for perf_events: CONFIG_PERF_EVENTS=y
# for stack traces: CONFIG_FRAME_POINTER=y
# kernel symbols: CONFIG_KALLSYMS=y
# tracepoints: CONFIG_TRACEPOINTS=y
# kernel function trace: CONFIG_FTRACE=y
# kernel-level dynamic tracing: CONFIG_KPROBES=y CONFIG_KPROBE_EVENTS=y
# user-level dynamic tracing: CONFIG_UPROBES=y CONFIG_UPROBE_EVENTS=y
# full kernel debug info: CONFIG_DEBUG_INFO=y
# kernel lock tracing: CONFIG_LOCKDEP=y
# kernel lock tracing: CONFIG_LOCK_STAT=y
# kernel dynamic tracepoint variables: CONFIG_DEBUG_INFO=y

检查系统性能

$ perf bench all
$ perf bench mem all

源码编译perf

确认系统支持perf特性后,即可通过源码进行编译。命令如下:

cd /usr/src
apt install -y libelf-dev systemtap-sdt-dev libaudit-dev libssl-dev libslang2-dev libperl-dev liblzma-dev libunwind-dev libdw-dev binutils-dev libiberty-dev asciidoc xmlto
apt install linux-source
tar -jxf linux-source-x.x.x.tar.bz2
apt install flex
cd linux-source-x.x.x/tools/perf
make
# 安装缺失的包后再make
ln -s  /usr/src/linux-source-x.x.x/tools/perf/perf /usr/local/bin/perf

FAQ

A:[xxx] with build id 6424687e552b0911efcbb4a096bc8d70d3c9af04 not found, continuing without symbols

Q: 分为两种情况。1. xxx为内核部分(也可以report查看是否为k级别),说明内核编译时缺少这部分符号表 2. xxx为软件执行程序(如no symbols found in /sbin/iscsid),说明应该安装此软件的debuginfo版本

A:perf.data 内缺失所有符号等信息

Q:编译perf时注意on所有必须选项(安装必须的dev版本依赖)

Auto-detecting system features:
...                         dwarf: [ on  ]
...            dwarf_getlocations: [ on  ]
...                         glibc: [ on  ]
...                          gtk2: [ OFF ]
...                      libaudit: [ on  ]
...                        libbfd: [ on  ]
...                        libcap: [ OFF ]
...                        libelf: [ on  ]
...                       libnuma: [ OFF ]
...        numa_num_possible_cpus: [ OFF ]
...                       libperl: [ on  ]
...                     libpython: [ OFF ]
...                     libcrypto: [ on  ]
...                     libunwind: [ on  ]
...            libdw-dwarf-unwind: [ on  ]
...                          zlib: [ on  ]
...                          lzma: [ on  ]
...                     get_cpuid: [ OFF ]
...                           bpf: [ on  ]
...                        libaio: [ on  ]
...                       libzstd: [ OFF ]
...        disassembler-four-args: [ on  ]

ref

Linux 性能示例 (brendangregg.com)

教程 - 性能维基 (kernel.org)

Linux 性能诊断 perf使用指南 - Digoal.Zhou’s Blog (billtian.github.io)

Linux perf Examples (brendangregg.com)

Perf Wiki (kernel.org)

系统讲解linux的性能热点分析手段 Performance Analysis and Optimization on Linux (cern.ch)