perf简介

perf是linux下的性能分析工具,主要有以下功能:

  1. 采集机器的pmu指标,分析程序的微架构特征,例如IPC、cache命中率;
  2. 分析程序热点、性能瓶颈。

1 perf 的安装

1.1 软件包安装

  1. centos环境 yum install perf
  2. ubuntu环境 apt-get install linux-tools-common linux-tools-generic linux-tools-$(uname -r)

1.2 从源码安装

# centos 环境

# 准备gcc >= 8.5.0,但是gcc版本太高也不行(gcc12),错误检查更严格
yum install centos-release-scl devtoolset-7-gcc -y
scl enable devtoolset-7 bash

# 安装依赖
yum install elfutils-devel binutils-devel numactl-devel libcap-devel -y

# 下载linux源码
curl -LO https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.15.tar.xz && tar xJf linux-5.16.15.tar.xz
cd linux-5.16.15/tools/perf && make -j $(nproc)

2 perf的对象

perf的对象可以是系统、CPU、进程,不同对象可以取交集。

  1. -a 对系统的所有进程、所有CPU;
  2. -C 对指定CPU,一个或多个,例如: -C 1,3,5,9-15 ;
  3. -p 对指定的进程,一个或多个,逗号分割;
  4. -t 对指定的线程,一个或多个,指定线程时,必须同时用 -p 指定进程;
  5. -C -p 同时指定CPU、进程。

perf的对象越多,perf的损耗越多,perf对象的数量如何计算呢?

  1. -a -C perf 对象和CPU数量一样;
  2. -p perf对象等于 CPU数量*进程的线程数量 这个数量可能非常大。

3 perf event

CPU本身有性能计数器(PMU),通过采集PMU的信息,可以评估(程序在)CPU的执行效率、内存带宽等。 perf 通过event选择对应的PMU,例如: instructions、cycles、branches

每个CPU的PUM数量是固定的,其中2个不可编程,用于采集cycles、instrctions,剩下的可用编程,用于指定对应的event。 如果采集的event数量多于PMU数量,perf会采用分时复用的方式使用PMU,准确性会下降。

备注

  1. event 是和CPU密切相关的,AMD和INTEL有很大不同,INTEL的不同CPU架构之间也有不小差异;
  2. event 是最原始的指标,需要组合原始指标才能得到更有意义的指标,例如: ipc = instructions/cycles;

3.1 event的指定

1、通过event的名称

perf list 用于列出所有的event,这个列表很长(取绝于perf的版本)

2、通过pmu事件号

例如: cpu/event=0xd3,umask=0x2,name=remote_dram/ 用于intel skylake架构,采集跨socket内存访问的次数。

perfmon 可以查到INTEL支持的事件,各个事件的含义。

3.2 event组

如前所讲,PMU可能会分时复用,可以控制每个分时的event, -e '{event1,event2,event3,event4}'

4 perf stat

perf stat 用于采集PMU信息,用于分析程序使用硬件的效率,最重要的是IPC。这涉及到微架构的内容,需要专门的文章总结。

4.1 基本用法

  1. perf stat -p <pid> --timeout <ms> 采集pid的基本信息,采集时长ms;
  2. perf stat -e cycles:k,instructions:k -p <pid> -- sleep <sec> 采集pid内核的指令,采集时长sec;
  3. perf stat -- test 运行test,采集基本信息。

4.2 输出介绍

下面是采集进程3926信息10秒的情况,整体分三部分:

  1. #号前是采集的值、名称,是从PMU读取的;
  2. #号后是计算的值,是根据PMU和其它信息计算的值;
  3. 括号中表示分时复用的情况。

例如:2.882 GHz 是CPU的频率,这个是计算的,不是采集的。

$ sudo perf stat -p 3926 -- sleep 10

 Performance counter stats for process id '261662':

      18810.424285      task-clock (msec)         #    1.875 CPUs utilized
            95,633      context-switches          #    0.005 M/sec
            19,360      cpu-migrations            #    0.001 M/sec
           149,140      page-faults               #    0.008 M/sec
    54,206,371,162      cycles                    #    2.882 GHz                      (98.05%)
    41,497,287,682      instructions              #    0.77  insn per cycle           (98.84%)
     7,903,861,194      branches                  #  420.185 M/sec                    (97.91%)
       140,183,559      branch-misses             #    1.77% of all branches          (97.12%)

      10.033397285 seconds time elapsed

备注

  1. 因为要操作硬件 perf stat 和内核版本关系密切,在新CPU上perf stat的(一些)功能可能无法使用;

5 perf record

perf record 用于采样程序的运行情况,包括程序的函数、调用栈(含内核)。 perf使用event作为事件源,按照一定的频率采样,记录采样时的函数,记录的次数越多,说明函数占比越大。

默认使用cycles采样,此时采样得到的就是函数耗时,传统意义上的火焰图。也可以使用l3-miss的event采样,得到的是函数l3-miss的耗时,用于优化内存访问。

5.1 perf record

采样程序的运行情况,常用参数:

  1. -g 记录程序调用栈;
  2. -o 指定输出文件,默认是perf.data;
  3. -F 采用频率。

5.2 perf report

查看perf record的结果,常用参数:

  1. -g none --no-children 不查看调用栈
  2. -d <dso> 查看该dso的符号

搜索特定符号的占比 perf report | grep -F'%' | tr -d'%' | awk '{sum += $1} END {print sum}'

5.3 perf top

函数调用top(类比top命令),相当于一边record一边report,比较消耗资源。

5.4 perf annotate

查看函数内部热点,例如:perf report显示foo函数占比大,可以用 perf annotate foo 显示函数foo内部哪段代码占比多,默认显示汇编,如果有源码,也会关联上源码。

但通常不怎么用,在使用perf record时,移动光标到foo函数,敲enter,就可以对foo做annotate。

5.5 perf script

解码、输出 perf record 的数据。

5.6 火焰图

如果不习惯perf report的文本界面,可以转成火焰图,(perf record时需要-g参数)。

# 需要用到火焰图工具:https://github.com/brendangregg/FlameGraph.git

# 采集30秒
perf record -g -p <pid> -o perfdata -- sleep 30

# perf script 解码perfdata
# stackcollapse-perf.pl(来自FlameGraph),把perf script的输出转成火焰图需要的文本格式
# flamegraph.pl 把火焰图文本转成火焰图
perf script -i perfdata | stackcollapse-perf.pl | flamegraph.pl > perf.svg

# 可以在stackcollapse之前,加工文本
# 例如:删除unknow栈,不区分线程
perf script -i perfdata | grep -vF '[unknown]' | sed -E 's/^[a-zA-Z0-9:_-]+/thread/' | stackcollapse-perf.pl | flamegraph.pl > perf.svg

注意事项:

  1. 为了展示内联函数, 为 perf record 指定 --call-graph dwarf 参数,使用dwarf来爬栈。但是会导致 perf.data 文件巨大,可用通过-F降低采样频率;

6 perf 对性能的影响

要弄清楚对性能的影响,需要了解perf的工作原理。

6.1 perf stat

perf stat 观察的是程序运行时,pmu计数器的值,这是个旁路流程,对程序影响很小。

6.2 perf record

perf record 观察的是程序运行时,程序的调用(函数、或栈),这个信息只有程序自己知道。程序需要响应中断,进入内核态,记录下中断发生时的调用地址、栈。这影响程序的性能,尤其是采样频率较高时。

7 常见问题

7.1 Too many events are opened

perf stat perf record 指定 -p 需要为进程的每个线程,每个CPU调用 perf_event_open 当线程数量较多,机器CPU数量较多时,会消耗大量fd(例如:5000线程、256个cpu,消耗128万),超出ulimit的限制。此时直接修改ulimit是不行的,需要先修改 fs.nr_open

$ perf record -e cycles:k -p 7921 -- sleep 30
Too many events are opened
Probably the maximum number of open file descriptors has been reached.
Hint: Try again after reducing the number of events.
Hint: Try increasing the limit with 'ulimit -n <limit>'

$ ulimit -n 2560000
-bash: ulimit: open files: cannot modify limit: Operation not permitted

# ulimit -n 修改失败,需要先修改 fs.nr_open
$ sysctl -w fs.nr_open=2560000

7.2 perf script 调用栈频率

perf script 输出调用栈,不能只看栈的输出频次,有个 period 字段,表示每个栈的耗时。FlameGraph 有个 issue 讨论这个问题。

7.3 全量采集、单独展示

如上所述,指定pid采集不见得开销就小,可以用 -a 全量采集,然后在perf report、perf script时指定 --pid 只展示单独的进程。

7.4 Java程序的符号

对于解释性语言而言,通常perf抓到的地址是解释器的、而不是被解释程序自身的。带JIT的语言(如Java)抓到的地址是JIT后的程序的,但是地址和符号的映射关系需要JIT单独提供。perf 在解析进程 <pid> 符号时,会访问 /tmp/perf-<pid>.map 。JIT提供的映射关系放到这个地址即可。

Java17提供了导出JIT符号的方法。 jcmd <pid> Compiler.perfmap

7.5 容器中使用perf的问题

容器通常没有root权限,需要配置sysctl采集内核符号, sysctl -w kernel.perf_event_paranoid=-1

Author: zhengzhiyong

Created: 2024-04-21 日 17:31

Validate