Linux之perf(4)stat统计

Author:Onceday Date:2023年9月16日

漫漫长路,才刚刚开始…

注:该文档内容采用了GPT4.0生成的回答,部分文本准确率可能存在问题

参考文档:

文章目录

1. 概述

perf是Linux内核提供的一个强大的性能分析工具,用于测量和分析系统和应用程序的性能。perf statsperf工具中的一个命令,用于收集并显示执行特定命令的统计信息。

perf stat的基本使用格式如下:

perf stat [options] [command]

当你运行perf stat命令时,它会在命令执行结束后显示一些统计信息,包括CPU周期、指令数、缓存引用、缓存未命中等。

下面是一些常见的perf stat选项:

  • -e--event:用于指定要测量的事件。你可以指定多个事件,用逗号隔开。
  • -p--pid:用于指定要测量的进程ID。
  • -r--repeat:用于指定要重复的次数。perf stat将输出所有运行的平均统计信息。
  • -d--detailed:显示详细的事件列表。

例如,如果你想知道运行ls命令需要多少CPU周期,你可以运行:

perf stat -e cycles ls

在终端里,你可能会看到类似这样的输出:

 Performance counter stats for 'ls':

         3,068,547 cycles                    # 0.001 GHz                     

       0.001072939 seconds time elapsed

这意味着ls命令执行了约306万个CPU周期。

常见用法如下:

   perf stat [-e <EVENT> | --event=EVENT] [-a] <command>
   perf stat [-e <EVENT> | --event=EVENT] [-a] -- <command> [<options>]
   perf stat [-e <EVENT> | --event=EVENT] [-a] record [-o file] -- <command> [<options>]
   perf stat report [-i file]

2. 参数介绍

2.1 参数概览

下面表格中列出当前帮助页面所提到的全部perf参数及其信息,不过难免有所不全或者错误的地方,还望海涵。

2.2 指定性能事件

perf stat的性能事件有几个来源,可通过-e指定,如下:

  • 符号事件名称(使用perf list列出所有事件)

  • 形如rN的原始PMU事件,其中N是一个十六进制值,表示具有与/sys/bus/event_source/devices/cpu/format/*中的条目描述的事件控制寄存器的布局相符的原始寄存器编码。

  • 后跟可选冒号和事件修饰符列表的符号或原始PMU事件,例如,cpu-cycles:p。有关事件修饰符的详细信息,请参阅perf-list(1)手册页。

  • 符号形式的事件,如pmu/param1=0x3,param2/,其中param1和param2被定义为:

    /sys/bus/event_source/devices/<pmu>/format/*中的PMU格式。

    'percore’是一个事件修饰符,它将一个核中的两个硬件线程的事件计数加总。例如:
    perf stat -A -a -e cpu/event,percore=1/,otherevent ...

  • 符号形式的事件,如pmu/config=M,config1=N,config2=K/,其中M、N、K是数字(十进制、十六进制、八进制格式)。每个config、config1和config2参数的可接受值由/sys/bus/event_source/devices/<pmu>/format/*中的相应条目定义。

请注意,最后两种语法支持在PMU名称中进行前缀和全局匹配,以简化在大型系统中跨多个相同类型PMU实例的事件创建(例如,内存控制器PMU)。多个PMU实例对于非核心PMU来说是典型的,所以在执行此匹配时,前缀’uncore_'也会被忽略。

2.3 限制子进程不继承计数器

perf stat 命令中的 -i--no-inherit 选项指定子任务不继承计数器。

在使用 perf 进行性能分析时,一种常见的做法是监视一个进程及其所有子进程。默认情况下,当你使用 perf 监视一个进程时,所有的子进程也会被监视,因为它们会继承父进程的性能计数器。

如果使用 -i--no-inherit 选项,那么只有你明确指定的进程会被监视,任何由这个进程创建的子进程都不会被监视。

这个选项在你只关心特定进程的性能,而不关心其子进程的性能时非常有用。例如,如果你正在运行一个多线程的应用,但只关心主线程的性能,那么你可以使用这个选项来防止 perf 监视其他线程。

2.4 统计指定bpf程序的性能

-b, --bpf-prog:在现有的bpf程序id上统计事件(逗号分隔的列表),需要root权限。可以使用bpftool-prog来查找系统中所有bpf程序的程序id。例如:

# bpftool prog | head -n 1
17247: tracepoint  name sys_enter  tag 192d548b9d754067  gpl

# perf stat -e cycles,instructions --bpf-prog 17247 --timeout 1000

Performance counter stats for 'BPF program(s) 17247':

85,967      cycles
28,982      instructions              #    0.34  insn per cycle

1.102235068 seconds time elapsed

上述命令表示在BPF程序17247上统计了事件"cycles"和"instructions",并在1000毫秒后超时。统计结果显示,该程序运行了85,967个周期,执行了28,982条指令,平均每个周期执行0.34条指令,总计花费了1.102235068秒。

2.5 多perf stat通过bpf共享硬件计数器

--bpf-counters:使用BPF程序从perf_events中聚合读数。这使得计算相同度量(如周期、指令等)的多个perf-stat会话可以共享硬件计数器。要默认在常见事件上使用BPF程序,请使用"perf config stat.bpf-counter-events=<list_of_events>"。例如:

perf config stat.bpf-counter-events=cycles,instructions

这将配置perf stat使用BPF程序默认在"cycles"和"instructions"事件上进行计数。这意味着,当多个perf-stat会话正在统计这些事件时,它们可以共享硬件计数器,从而提高资源利用率。

2.6 指定bpf共享计数器的哈希表路径

--bpf-attr-map:与选项"–bpf-counters"一起使用时,不同的perf-stat会话通过固定的哈希映射共享关于共享的BPF程序和映射的信息。使用"–bpf-attr-map"来指定这个固定哈希映射的路径。默认路径是/sys/fs/bpf/perf_attr_map。例如:

perf stat --bpf-counters --bpf-attr-map /path/to/your/map

在这个例子中,"/path/to/your/map"应该被替换为你的固定哈希映射的实际路径。使用这个选项,可以更灵活地管理和共享BPF程序和映射的信息。

2.7 计数器的值缩放和标准化

--no-scale:不对计数器的值进行缩放/标准化。

默认情况下,perf stat会根据实际采样时间与可能的最大采样时间(比如,CPU周期数)的比例来缩放性能计数器的值。这是为了在处理器被其他进程或系统共享时,提供更准确的计数。

使用--no-scale选项,perf stat将不会进行这种缩放,而是直接报告原始的计数器值。这在你需要获取未经处理的原始数据时可能会很有用。例如:

perf stat --no-scale -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件的未缩放的计数值。

2.8 打印更详细的统计事件信息

-d, --detailed:打印更详细的统计信息,可以指定最多3次。

  • -d:详细事件,L1和LLC数据缓存

  • -d -d:更详细的事件,包括dTLB和iTLB事件

  • -d -d -d:非常详细的事件,增加预取事件

例如:

perf stat -d -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,并提供关于L1和LLC数据缓存的详细信息。

perf stat -d -d -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,并提供关于L1和LLC数据缓存,还有dTLB和iTLB事件的更详细的信息。

perf stat -d -d -d -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,并提供关于L1和LLC数据缓存,dTLB和iTLB事件,还有预取事件的非常详细的信息。

2.9 重复执行命令

-r, --repeat=<n>:重复执行命令,并打印平均值和标准差(最大值100)。0表示无限重复。

例如:

perf stat -r 5 -e cycles,instructions

在这个例子中,perf stat将会执行5次,每次都会统计"cycles"和"instructions"事件。然后,它将计算这些事件的平均值和标准差,并将结果打印出来。

如果你想要无限次地重复执行命令,你可以使用0作为参数:

perf stat -r 0 -e cycles,instructions

在这个例子中,perf stat将会无限次地统计"cycles"和"instructions"事件,直到你手动停止它。

2.10 使用千位分隔符打印大数字

-B, --big-num:根据区域设置,使用千位分隔符打印大数字。默认情况下是启用的。使用"–no-big-num"来禁用。默认设置可以通过"perf config stat.big-num=false"来改变。

例如:

perf stat -B -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,并用千位分隔符来打印这些大数字。

如果你不想使用千位分隔符,你可以使用--no-big-num来禁用这个功能:

perf stat --no-big-num -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,并直接打印这些数字,不使用千位分隔符。

你也可以改变这个功能的默认设置,通过执行下面的命令:

perf config stat.big-num=false

执行这个命令后,perf stat将默认不使用千位分隔符。你可以通过指定-B选项来临时启用它。

2.11 指定CPU进行计数

-C, --cpu=:仅在提供的CPU列表上计数。可以提供多个CPU,作为一个没有空格的逗号分隔的列表:0,1。CPU范围用-指定:0-2。在每线程模式下,此选项被忽略。仍然需要-a选项来激活系统范围的监控。默认是在所有CPU上计数。

例如:

perf stat -C 0,1 -e cycles,instructions

在这个例子中,perf stat将只在CPU 0和CPU 1上统计"cycles"和"instructions"事件。

你也可以指定一个范围的CPU:

perf stat -C 0-2 -e cycles,instructions

在这个例子中,perf stat将只在CPU 0, CPU 1和CPU 2上统计"cycles"和"instructions"事件。

注意,要激活系统范围的监控,你还需要添加-a选项:

perf stat -a -C 0-2 -e cycles,instructions

在这个例子中,perf stat将在整个系统(但只在CPU 0, CPU 1和CPU 2上)统计"cycles"和"instructions"事件。

2.12 每个cpu统计结果单独显示

-A, --no-aggr:不跨所有被监控的CPU聚合计数。

默认情况下,perf stat会将所有被监控的CPU的计数聚合在一起。如果你使用了-A--no-aggr选项,perf stat将会为每个被监控的CPU单独显示计数。

例如:

perf stat -A -e cycles,instructions

在这个例子中,perf stat将会为每个被监控的CPU单独统计"cycles"和"instructions"事件,而不是将所有CPU的计数聚合在一起。这可以让你更清楚地了解每个CPU的性能情况。

2.13 空运行(测量本身开销)

-n, --null:空运行 - 不启动任何计数器。

这可以用来只测量经过的墙钟时间 - 或者评估perf stat自身的原始开销,而不运行任何计数器。例如:

perf stat -n

在这个例子中,perf stat将不启动任何性能计数器。这可以用来测量执行某个指令的纯墙钟时间(即实际经过的时间),或者评估perf stat命令本身的开销。

这个选项在你只关心命令的实际运行时间,或者想要测量perf stat的开销时,可能会很有用。

2.14 按照指定分隔符打印统计数据

-x SEP, --field-separator SEP:使用CSV风格的输出打印计数,以便于直接导入到电子表格中。列由SEP中指定的字符串分隔。例如:

perf stat -x, -e cycles,instructions

在这个例子中,perf stat将会使用逗号作为字段分隔符,统计"cycles"和"instructions"事件。结果会以CSV格式输出,可以轻松地导入到像Excel这样的电子表格软件中。

你可以指定任何你想要的字符串作为字段分隔符:

perf stat -x; -e cycles,instructions

在这个例子中,字段分隔符是分号。

2.15 用表格形式显示多次重复统计的数据

--table:在表格格式中显示每次运行的时间(-r选项),例如:

perf stat --null -r 5 --table perf bench sched pipe

命令执行后的输出可能类似于:

Performance counter stats for 'perf bench sched pipe' (5 runs):

# Table of individual measurements:
5.189 (-0.293) #
5.189 (-0.294) #
5.186 (-0.296) #
5.663 (+0.181) ##
6.186 (+0.703) ####

# Final result:
5.483 +- 0.198 seconds time elapsed  ( +-  3.62% )

在这个例子中,perf stat将会执行perf bench sched pipe命令5次,并显示每次运行的时间。这些时间将以表格格式输出,每行代表一次运行,显示该次运行的时间以及相对于平均时间的差异。最后,它还会显示总的平均时间和标准差。

2.16 监控指定cgroup里面的线程

-G name, --cgroup name:只在名为"name"的容器(cgroup)中进行监控。这个选项只在每CPU模式中可用。cgroup文件系统必须被挂载。所有属于"name"容器的线程在运行在被监控的CPU上时都会被监控。可以提供多个cgroup。每个cgroup将应用于相应的事件,即,第一个cgroup应用于第一个事件,第二个cgroup应用于第二个事件,依此类推。可以使用,例如,-G foo,,bar提供一个空的cgroup(始终监控)。cgroup必须有相应的事件,即,它们总是引用命令行上早先定义的事件。如果用户想要跟踪某个特定cgroup的多个事件,用户可以使用-e e1 -e e2 -G foo,foo或者只使用-e e1 -e e2 -G foo

如果想要监控,比如说,一个cgroup的cycles,也想要全系统监控cycles,可以使用这条命令行:perf stat -e cycles -G cgroup_name -a -e cycles。例如:

perf stat -e cycles -G my_cgroup -a -e cycles

在这个例子中,perf stat将会在名为"my_cgroup"的cgroup中统计"cycles"事件,并且还会全系统地统计"cycles"事件。

2.17 给每个cgroup指定相同的事件

--for-each-cgroup name:为"name"中的每个cgroup扩展事件列表(允许用逗号分隔多个cgroups)。它也支持正则表达式匹配多个组。这与为每个事件x名称重复-e选项和-G选项的效果相同。此选项不能与-G/–cgroup选项一起使用。

例如:

perf stat --for-each-cgroup cgroup1,cgroup2 -e cycles,instructions

在这个例子中,perf stat将为"cgroup1"和"cgroup2"中的每个cgroup单独统计"cycles"和"instructions"事件。这与下面的命令具有相同的效果:

perf stat -e cycles -G cgroup1 -e instructions -G cgroup1 -e cycles -G cgroup2 -e instructions -G cgroup2

此选项能使命令行更简洁,特别是当你需要为多个cgroup统计多个事件时。同时,它也支持使用正则表达式来匹配多个cgroup,这可以使得cgroup的选择更灵活。

2.18 追加统计输出到指定的文件中

--append:将输出追加到使用-o选项指定的文件。如果没有指定-o,则忽略此选项。

例如:

perf stat -o output.txt --append -e cycles,instructions

在这个例子中,perf stat将会统计"cycles"和"instructions"事件,然后将输出追加到名为"output.txt"的文件。如果"output.txt"已经存在,并且包含了旧的数据,那么新的数据将不会覆盖旧的数据,而是添加到文件的末尾。

这个选项在你想要在同一个文件中收集多次运行的数据时可能会很有用。

2.19 将输出日志到指定文件中

--log-fd:将输出日志到文件描述符(fd),而不是stderr。这个选项与--output互补,并且与其互斥。可以在此处使用--append。例如:

3>results perf stat --log-fd 3 -- $cmd
3>>results perf stat --log-fd 3 --append -- $cmd

在这些例子中,perf stat将会将输出日志到文件描述符3,该文件描述符关联到文件"results"。在第一个例子中,如果文件"results"已经存在,那么它的内容将会被新的输出替换。在第二个例子中,如果文件"results"已经存在,那么新的输出将会添加到文件的末尾,不会覆盖旧的内容。

这个选项在你想要将输出重定向到特定的文件描述符,而不是默认的stderr时可能会很有用。

2.20 动态开启/关闭测量

--control=fifo:ctl-fifo[,ack-fifo], --control=fd:ctl-fd[,ack-fd]:这两个选项都被用来在perf stat运行期间动态地控制测量。它们允许你在测量运行时启用或禁用事件。

ctl-fifo / ack-fifo开启并用作ctl-fd / ack-fd。在ctl-fd描述符上监听控制命令(启用:启用事件,禁用:禁用事件)。可以使用--delay=-1选项以禁用事件状态启动测量。可选地,发送控制命令完成(ack\n)到ack-fd描述符以与控制过程同步。

以下是在测量期间启用和禁用事件的bash shell脚本示例:

#!/bin/bash

ctl_dir=/tmp/

ctl_fifo=${ctl_dir}perf_ctl.fifo
test -p ${ctl_fifo} && unlink ${ctl_fifo}
mkfifo ${ctl_fifo}
exec {ctl_fd}<>${ctl_fifo}

ctl_ack_fifo=${ctl_dir}perf_ctl_ack.fifo
test -p ${ctl_ack_fifo} && unlink ${ctl_ack_fifo}
mkfifo ${ctl_ack_fifo}
exec {ctl_fd_ack}<>${ctl_ack_fifo}

perf stat -D -1 -e cpu-cycles -a -I 1000       \
          --control fd:${ctl_fd},${ctl_fd_ack} \
          -- sleep 30 &
perf_pid=$!

sleep 5  && echo 'enable' >&${ctl_fd} && read -u ${ctl_fd_ack} e1 && echo "enabled(${e1})"
sleep 10 && echo 'disable' >&${ctl_fd} && read -u ${ctl_fd_ack} d1 && echo "disabled(${d1})"

exec {ctl_fd_ack}>&-
unlink ${ctl_ack_fifo}

exec {ctl_fd}>&-
unlink ${ctl_fifo}

wait -n ${perf_pid}
exit $?

在这个脚本中,perf stat首先以禁用的状态启动测量,然后在5秒后启用测量,在10秒后禁用测量。这是通过发送"enable"和"disable"命令到ctl-fd来实现的。ack-fd用于接收命令完成的确认。

2.21 测量预处理和后处理钩子函数

--pre, --post:这是预测和测后钩子,例如:

perf stat --repeat 10 --null --sync --pre make -s O=defconfig-build/clean -- make -s -j64 O=defconfig-build/bzImage

在这个例子中,perf stat将会进行10次测量。每次测量之前,都会运行make -s O=defconfig-build/clean命令(由--pre指定)。测量的目标命令是make -s -j64 O=defconfig-build/bzImage

--pre--post钩子在你需要在测量之前和/或之后运行某些命令时可能会很有用,例如清理缓存、设置环境变量或检查状态等。这些钩子命令不会被计入测量的结果。

2.22 间隔指定时间打印计数差值

-I msecs, --interval-print msecs:每隔N毫秒打印计数差(最小值:1ms)。在某些情况下,开销百分比可能会很高,例如在小于100ms的间隔内。谨慎使用。例如:

perf stat -I 1000 -e cycles -a sleep 5

在这个例子中,perf stat将统计"cycles"事件,然后在每隔1000毫秒(1秒)的间隔内打印一次计数差。sleep 5命令的执行时间为5秒,因此这将打印5次计数差。

如果度量存在,它将由在此间隔内产生的计数计算,然后度量在#之后打印。这个选项在你需要实时监视事件计数的变化时可能会很有用,比如在进行性能分析或故障排查时。

--interval-count times:固定次数的打印计数差。这个选项应该与"-I"选项一起使用。例如:

perf stat -I 1000 --interval-count 2 -e cycles -a

在这个例子中,perf stat将会统计"cycles"事件,并且在每隔1000毫秒(1秒)的间隔内打印一次计数差。但是,由于--interval-count设置为2,所以只会打印两次计数差,然后就停止打印,即使测量可能还在继续。

这个选项在你只关心一定次数的计数差,而不关心后续的计数差时可能会很有用。例如,你可能只关心启动阶段的性能数据,或者只关心特定操作的性能数据。

2.23 在指定时间后停止统计

--timeout msecs:在N毫秒后停止perf stat会话并打印计数差(最小值:10 ms)。这个选项不支持与"-I"选项一起使用。例如:

perf stat --timeout 2000 -e cycles -a

在这个例子中,perf stat将会统计"cycles"事件,然后在2000毫秒(2秒)后停止会话并打印计数差。即使目标进程可能还在运行,测量也会在2秒后停止。

这个选项在你只关心测量的前N毫秒,或者你想限制测量的总体时间时可能会很有用。例如,你可能只关心启动阶段的性能数据,或者你可能想要避免长时间测量导致的过大开销。

2.24 只打印计算的度量

--metric-only:仅打印计算的度量。将它们在单行内打印。不显示任何原始值。不支持与--per-thread一起使用。

这个选项将只打印计算出的度量值,而不是每个事件的原始计数。这可能在你只关心特定度量(比如IPC或者缓存命中率)而不关心单个事件的计数时很有用。

请注意,这个选项不支持与--per-thread一起使用,所以你不能用它来获得每个线程的度量值。

2.25 按每个插槽/处理器/物理核操作来测量

--per-socket:对于系统范围的测量,按处理器插槽聚合计数。这是一个有用的模式,用于检测插槽之间的不平衡。要启用此模式,请除了-a(系统范围)之外,还使用--per-socket。输出包括插槽号和该插槽上的在线处理器数量。这对于评估聚合量很有用。

例如,如果你在一个具有两个插槽的系统上进行测量,并且每个插槽上有多个核心,你可以使用--per-socket选项来获取每个插槽的聚合计数。这可能在你需要检测插槽之间的负载不平衡时很有用。例如,如果一个插槽的计数显著高于另一个插槽,这可能表明你的应用程序或系统配置没有正确地使用所有可用的处理器资源。

--per-die:对于系统范围的测量,按处理器Die("die"是指在单个硅晶圆上切割出来的单个处理器或芯片)聚合计数。这是一个用于检测Die间不平衡的有用模式。要启用此模式,请除了-a(系统范围)之外,还使用--per-die。输出包括Die号和该Die上的在线处理器数量。这对于评估聚合量很有用。

例如,如果你在一个具有多个Die的系统上进行测量,并且每个Die中有多个核心,你可以使用--per-die选项来获取每个Die的聚合计数。这可能在你需要检测Die之间的负载不平衡时很有用。例如,如果一个Die的计数显著高于另一个Die,这可能表明你的应用程序或系统配置没有正确地使用所有可用的处理器资源。

--per-core:对于系统范围的测量,按物理处理器聚合计数。这是一个用于检测物理核心之间不平衡的有用模式。要启用此模式,请除了-a(系统范围)之外,还使用--per-core。输出包括核心编号和该物理处理器上的在线逻辑处理器数量。

例如,如果你在一个具有多个物理核心的系统上进行测量,并且每个物理核心上有多个逻辑核心(例如,使用了超线程技术),你可以使用--per-core选项来获取每个物理核心的聚合计数。这可能在你需要检测物理核心之间的负载不平衡时很有用。例如,如果一个物理核心的计数显著高于另一个核心,这可能表明你的应用程序或系统配置没有正确地使用所有可用的处理器资源。

--per-node:对于系统范围的测量,按NUMA节点(Non-Uniform Memory Access,非均匀内存访问)聚合计数。这是一个用于检测NUMA节点间不平衡的有用模式。要启用此模式,请除了-a(系统范围)之外,还使用--per-node

例如,如果你在一个具有多个NUMA节点的系统上进行测量,你可以使用--per-node选项来获取每个节点的聚合计数。这可能在你需要检测NUMA节点之间的负载不平衡时很有用。例如,如果一个节点的计数显著高于另一个节点,这可能表明你的应用程序或系统配置没有正确地使用所有可用的内存和处理器资源。

NUMA是一种用于多处理器系统的内存设计,其中处理器可以更快地访问其"本地"内存(即与其在同一节点上的内存)比访问"远程"内存(即在其他节点上的内存)。在理想情况下,一个良好优化的应用程序或系统配置会尽可能地利用本地内存,以减少内存访问延迟并提高性能。

2.26 统计事务执行的信息

-T, --transaction:如果支持,打印事务执行的统计信息。

事务执行是一种处理器特性,它允许一组指令被原子性地执行。也就是说,这组指令要么全部成功执行,要么在执行过程中如果发生错误(例如,由于并发冲突)则全部不执行。事务执行有助于简化多线程编程,因为它可以自动处理复杂的并发控制问题。

如果你的处理器支持事务执行(例如,Intel的TSX特性),那么-T--transaction选项可以用来打印事务执行的统计信息。这可能包括成功完成的事务数量,以及由于各种原因(例如,资源限制或冲突)而中止的事务数量。这些信息可以帮助你理解你的代码如何使用事务执行,以及是否存在可以优化的地方。

2.27 事件复用

--metric-no-group:默认情况下,用于计算度量的事件被放置在弱组中。该组试图强制调度所有的事件或者不调度任何事件。--metric-no-group选项将事件放置在组之外,可能会增加事件被调度的机会 - 导致更高的准确性。然而,由于事件可能不再一起被调度,因此像每周期指令数这样的度量的准确性可能会降低 - 因为这两个度量可能不再同时被测量。

这个选项可能对你在考虑使用哪种度量方式时有所帮助。默认情况下,perf会尽量确保用于计算度量的所有事件都在同一时间进行测量。这可以提高度量的准确性,因为它可以确保所有相关的事件都在同一时间窗口内发生。然而,这也可能限制了一些事件的调度,因为处理器可能有限制,不允许同时测量太多的事件。

如果你使用--metric-no-group选项,perf将不再尝试将所有事件放入同一组。这可能会增加某些事件被测量的机会,但也可能导致度量的准确性降低,因为不再能够保证所有相关的事件都在同一时间窗口内发生。

--metric-no-merge:默认情况下,如果一个组包含了另一个组所需的所有事件,那么不同弱组中的度量事件可以共享。在这种情况下,一个组将被消除,减少事件复用,并使得某些度量组的总和达到100%。共享组的一个缺点是该组可能需要复用,因此对于不需要复用的小组的准确性就会降低。此选项禁止事件合并逻辑在组之间共享事件,可以在这种情况下提高准确性。

在性能分析中,事件复用是一种常见的策略,用于在处理器的硬件性能计数器数量有限的情况下,测量更多的事件。通过在不同的时间片中测量不同的事件,可以实现对更多事件的测量。然而,这种方法可能会降低测量的准确性,因为在不同的时间片中测量的事件可能无法准确地反映它们在同一时间窗口内的行为。

--metric-no-merge选项可以用来禁止perf在不同的事件组之间共享事件,从而提高测量的准确性。这可能在你需要尽可能准确地测量某些事件,而不太关心其他事件时有所帮助。然而,应该注意,使用这个选项可能会增加事件复用的需要,因此可能会降低对其他事件的测量准确性。

3. 特性

3.1 数据报告

perf stat 可以读取并报告 perf 数据文件的统计数据。

  • -i file, --input file:输入文件名。

  • --per-socket:在系统范围内的度量中,按处理器插槽聚合计数。

  • --per-die:在系统范围内的度量中,按处理器芯片聚合计数。

  • --per-core:在系统范围内的度量中,按物理处理器聚合计数。

  • -M, --metrics:打印用逗号分隔列表指定的指标或指标组。对于一个组,将添加该组的所有指标。这些指标中的事件将自动被测量。可以查看 perf list 的输出以了解可能的指标和指标组。

  • -A, --no-aggr:不跨所有监视的 CPU 聚合计数。

  • --topdown:打印 CPU 支持的自顶向下指标。这允许通过将消耗的周期分解为前端受限、后端受限、猜测错误和退休来确定 CPU 管道中的瓶颈,这适用于 CPU 绑定的工作负载。

  • --td-level:打印与输入级别相等的自顶向下统计信息。它允许用户打印感兴趣的自顶向下指标级别,而非只有第一级的自顶向下指标。

  • --no-merge:不合并来自同一 PMU 的结果。

  • --hybrid-merge:合并所有 PMU 的混合事件计数。

  • --smi-cost:如果支持 msr/aperf/ 和 msr/smi/ 事件,测量 SMI 的成本。

  • --all-kernel:配置所有使用的事件在内核空间运行。

  • --all-user:配置所有使用的事件在用户空间运行。

  • --percore-show-thread:事件修饰符 “percore” 支持对核心内所有硬件线程的事件计数进行求和,并按核心显示计数。

  • --summary:打印间隔模式(-I)的概要。

  • --no-csv-summary:不在 CVS 概要输出的第一列打印概要。此选项必须与 -x 和 --summary 一起使用。

  • --cputype:只在具有此类型的 cpu 上启用事件,适用于混合平台(例如:core 或 atom)。

--topdown:打印CPU支持的自顶向下度量。这允许通过将消耗的周期分解为前端限制、后端限制、错误推测和退役,来确定CPU绑定工作负载在CPU管道中的瓶颈。

自顶向下(Top-down)的性能分析是一种从宏观角度理解CPU性能瓶颈的方法。它通过将CPU执行周期分解为几个主要类别,来帮助你理解哪个部分最可能限制应用程序的性能。这些类别包括:

  • 前端限制(Frontend bound):CPU在获取和解码指令时花费的时间。如果这个值过高,可能表示你的代码的指令获取或分支预测效率不高。
  • 后端限制(Backend bound):CPU在执行指令时花费的时间。如果这个值过高,可能表示你的代码的内存访问或算术运算效率不高。
  • 错误推测(Bad speculation):CPU由于错误的分支预测或其他推测执行机制而浪费的时间。如果这个值过高,可能表示你的代码的分支预测效率不高。
  • 退役(Retiring):CPU成功完成和退役指令的时间。如果这个值过高,表示你的代码在CPU上的执行效率较高。

--topdown选项可以用来打印这些度量,帮助你理解你的代码在哪些方面可能需要优化。注意,这个选项需要CPU支持Top-Down分析方法,并且可能不适用于所有的CPU型号。

“前端受限”(Frontend bound)意味着 CPU 不能足够快地获取和解码指令。“后端受限”(Backend bound)意味着计算或内存访问是瓶颈。“猜测错误”(Bad Speculation)意味着 CPU 由于分支预测错误和类似问题浪费了周期。“退休”(Retiring)意味着 CPU 在没有明显瓶颈的情况下进行了计算。但这些瓶颈只有在工作负载实际受 CPU 限制,而不是受其他因素限制时,才是真正的瓶颈。

为了获得最好的结果,通常建议使用间隔模式,比如 -I 1000,因为工作负载的瓶颈可能经常变化。

这将启用 --metric-only,除非使用 --no-metric-only 覆盖。

以下限制只适用于旧的 Intel CPUs 和 Atom,在新的 CPUs(IceLake 以及后续版本)上,可以为任何线程收集 TopDown 数据:

TopDown 指标是按核心而不是按 CPU 线程收集的。自动启用每个核心模式,需要 -a(全局监控),这需要 root 权限或设置 perf.perf_event_paranoid=-1

TopDown 使用了完整的性能监测单元,需要禁用 NMI watchdog(作为 root):使用 echo 0 > /proc/sys/kernel/nmi_watchdog 可以获得最好的结果。否则,随着工作负载阶段的变化,瓶颈可能会不一致。

为了解释结果,通常需要知道工作负载在哪些 CPU 上运行。如果需要,可以使用 taskset 强制 CPU。

--td-level:打印与输入级别相等的自顶向下统计信息。它允许用户打印感兴趣的自顶向下指标级别,而非只有第一级的自顶向下指标。

由于更高的级别收集了更多的指标并使用了更多的计数器,它们的准确性会降低。按照惯例,可以通过在指标后面添加 _group 来查看一个指标,这将比收集一个级别的所有指标的准确性更高。例如,一级分析可能会突出 tma_frontend_bound。可以使用 perf stat -M tma_frontend_bound_group... 进一步深入这个指标。

如果输入的级别高于支持的最大级别,则会报错。

3.2 消耗时间显示

在上述示例中,我们可以显示三种类型的计时。我们总是显示启用/活跃计数器的时间:

83.409183620 seconds time elapsed

对于工作负载会话,我们还显示工作负载在用户/系统空间花费的时间:

74.684747000 seconds user
8.739217000 seconds sys

这些时间与 time 工具显示的时间完全相同。以下是对这些时间的解释:

  • 83.409183620 seconds time elapsed:这是从启动到结束的实际墙钟时间(也称为经过的时间)。这包括所有的等待时间(例如 I/O)、系统时间和用户时间。

  • 74.684747000 seconds user:这是在用户模式下执行的 CPU 时间,也就是说,这段时间内 CPU 在执行应用程序代码,而不是内核代码。

  • 8.739217000 seconds sys:这是在系统(或内核)模式下执行的 CPU 时间,即 CPU 在执行操作系统内核代码的时间。

通过这些时间,我们可以了解应用程序在用户空间和内核空间的行为,以及总体上应用程序的运行时间。

3.3 CSV输出数据字段

使用 -x 选项,perf stat 能够输出一个非标准的 CSV(逗号分隔值)格式的输出。输出中的逗号并没有被置于 "" 中。为了便于解析,建议使用不同的字符,如 -x \;。字段的顺序如下:

  • 可选的微秒时间戳(小数秒),需要 -I xxx 选项

  • 可选的 CPU,核心,或套接字标识符

  • 可选的被聚合的逻辑 CPU 数量

  • 计数器的值

  • 计数器值的单位,或者为空

  • 事件名称

  • 计数器的运行时间

  • 计数器运行的时间占测量时间的百分比

  • 如果使用 -r 收集了多个值,则会有可选的方差

  • 可选的度量值

  • 度量的单位,或者为空

如果打印了额外的度量,所有之前的字段可能都为空。

这种输出格式允许用户更容易地解析和使用 perf stat 的结果,特别是在自动化工具或脚本中。例如,可以使用这种格式将 perf stat 的结果导入到电子表格或数据库中进行进一步的分析和可视化。

3.4 混合异构cpu统计

在某些 Intel 平台中,如 AlderLake,perf 工具支持混合事件(hybrid events)。AlderLake 是一个混合平台,由 Atom CPU 和 Core CPU 组成。每个 CPU 都有专属的事件列表。部分事件在 Core CPU 上可用,部分事件在 Atom CPU 上可用,甚至有部分事件在两者上都可用。

内核通过 sysfs 导出两个新的 CPU PMUs:/sys/devices/cpu_core/sys/devices/cpu_atom

在这些目录下创建了 cpus 文件。例如:

cat /sys/devices/cpu_core/cpus 0-15
cat /sys/devices/cpu_atom/cpus 16-23

这表明 cpu0-cpu15 是 Core CPUs,而 cpu16-cpu23 是 Atom CPUs。

像以前一样,使用 perf-list 来列出符号化事件。

perf list

例如,以下输出:

inst_retired.any [Fixed Counter: Counts the number of instructions retired. Unit: cpu_atom]
inst_retired.any [Number of instructions retired. Fixed Counter - architectural event. Unit: cpu_core]

在简短描述中增加了 Unit: xxx 来表示事件属于哪个 PMU。相同的事件名但属于不同的 PMU 是可以被支持的。

如果要启用特定 PMU 的混合事件,支持以下语法:

cpu_core/<event name>/

cpu_atom/<event name>/

例如,计算 Core CPUs 上的 cycles 事件:

perf stat -e cpu_core/cycles/

当创建一个在 Atom 和 Core 两者上都可用的事件时,将自动创建两个事件。一个用于 Atom,另一个用于 Core。大多数硬件事件和缓存事件都在 cpu_core 和 cpu_atom 上可用。

对于硬件事件,它们有预定义的配置(例如,0 表示 cycles)。但在混合平台上,内核需要知道事件来自于哪里(来自 Atom 还是来自 Core)。原始的 perf 事件类型 PERF_TYPE_HARDWARE 不能携带 PMU 信息。因此现在这个类型被扩展为支持 PMU 的类型。PMU 类型 ID 存储在 attr.config[63:32] 中。

PMU 类型 ID 可以从 sysfs 中获取。/sys/devices/cpu_atom/type/sys/devices/cpu_core/type

对于 PERF_TYPE_HARDWARE 的新 attr.config 布局:

PERF_TYPE_HARDWARE: 0xEEEEEEEE000000AA 
AA: hardware event ID
EEEEEEEE: PMU type ID

缓存事件也类似。类型 PERF_TYPE_HW_CACHE 被扩展为支持 PMU 的类型。PMU 类型 ID 存储在 attr.config[63:32] 中。

对于 PERF_TYPE_HW_CACHE 的新 attr.config 布局:

PERF_TYPE_HW_CACHE: 0xEEEEEEEE00DDCCBB 
BB: hardware cache ID 
CC: hardware cache op ID 
DD: hardware cache op result ID 
EEEEEEEE: PMU type ID

如果没有指定 PMU 就启用硬件事件,例如,perf stat -e cycles -a(在这个例子中使用的是系统范围),将自动创建两个事件。例如:

perf_event_attr:
  size                             120
  config                           0x400000000
  sample_type                      IDENTIFIER
  read_format                      TOTAL_TIME_ENABLED|TOTAL_TIME_RUNNING
  disabled                         1
  inherit                          1
  exclude_guest                    1

perf_event_attr:
  size                             120
  config                           0x800000000
  sample_type                      IDENTIFIER
  read_format                      TOTAL_TIME_ENABLED|TOTAL_TIME_RUNNING
  disabled                         1
  inherit                          1
  exclude_guest                    1

type 0 代表 PERF_TYPE_HARDWARE0x40x400000000 中表示这是 cpu_core PMU(Performance Monitoring Unit,性能监控单元)。0x80x800000000 中表示这是 cpu_atom PMU(atom PMU 类型 ID 是随机的)。

内核在 cpu0-cpu15(核心 CPU)上创建 cycles (0x400000000),并在 cpu16-cpu23atom CPU)上创建 cycles (0x800000000)。

对于 perf-stat 结果,它显示了两个事件:

Performance counter stats for 'system wide':

6,744,979      cpu_core/cycles/
1,965,552      cpu_atom/cycles/

第一个 cycles 是核心事件,第二个 cyclesatom 事件。

线程模式示例:

perf-stat 为混合事件报告了缩放计数,并显示了百分比。百分比是事件的运行时间/启用时间。

例如,triad_loopcpu16 (atom 核心)上运行,而我们可以看到核心循环的缩放值为 160,444,092,百分比为 0.47%

perf stat -e cycles -- taskset -c 16 ./triad_loop

和之前一样,创建了两个事件。

.ft C
perf_event_attr:
  size                             120
  config                           0x400000000
  sample_type                      IDENTIFIER
  read_format                      TOTAL_TIME_ENABLED|TOTAL_TIME_RUNNING
  disabled                         1
  inherit                          1
  enable_on_exec                   1
  exclude_guest                    1
.ft

and

.ft C
perf_event_attr:
  size                             120
  config                           0x800000000
  sample_type                      IDENTIFIER
  read_format                      TOTAL_TIME_ENABLED|TOTAL_TIME_RUNNING
  disabled                         1
  inherit                          1
  enable_on_exec                   1
  exclude_guest                    1
.ft

Performance counter stats for 'taskset -c 16 ./triad_loop':

233,066,666      cpu_core/cycles/                                              (0.43%)
604,097,080      cpu_atom/cycles/                                              (99.57%)

perf-record

如果在 perf record 中没有指定 -e,在混合平台上,它会创建两个默认的 cycles 并将它们添加到事件列表中。一个是用于核心的,另一个是用于 atom 的。

perf-stat

如果在 perf stat 中没有指定 -e,在混合平台上,除了软件事件外,将按顺序创建以下事件并将它们添加到事件列表中。

cpu_core/cycles/, cpu_atom/cycles/, cpu_core/instructions/,
cpu_atom/instructions/, cpu_core/branches/, cpu_atom/branches/,
cpu_core/branch-misses/, cpu_atom/branch-misses/

当然,perf-statperf-record 都支持使用特定的 PMU 启用混合事件。

例如:

perf stat -e cpu_core/cycles/ perf stat -e cpu_atom/cycles/
perf stat -e cpu_core/r1a/ perf stat -e cpu_atom/L1-icache-loads/
perf stat -e cpu_core/cycles/,cpu_atom/instructions/ perf stat -e
{cpu_core/cycles/,cpu_core/instructions/}

但是 {cpu_core/cycles/,cpu_atom/instructions/} 将返回警告并禁用分组,因为分组中的 PMUs 不匹配 (cpu_core vs. cpu_atom)。

3.5 Json输出格式

perf stat-j 选项可以生成 JSON 格式的输出,这种格式更容易被其他程序或脚本解析。下面是 JSON 输出中每个字段的简要描述:

  • timestamp:这是一个可选字段,表示微秒时间戳。只有在使用 -I 选项时才可用。

  • core:表示核心标识符,只有在使用 --per-core 选项时才可用。

  • die:表示 die 标识符,只有在使用 --per-die 选项时才可用。

  • socket:表示套接字标识符,只有在使用 --per-socket 选项时才可用。

  • node:表示节点标识符,只有在使用 --per-node 选项时才可用。

  • thread:表示线程标识符,只有在使用 --per-thread 选项时才可用。

  • counter-value:表示计数器值。

  • unit:表示计数器值的单位。如果没有适用的单位,此字段为空。

  • event:表示事件的名称。

  • variance:这是一个可选字段,表示如果收集多个值的方差。只有在使用 -r 选项时才可用。

  • runtime:表示计数器的运行时间。

  • metric-value:这是一个可选字段,表示度量值。

  • metric-unit:这是一个可选字段,表示度量的单位。

以下是 JSON 输出可能的样子:

{
    "timestamp": 1631879160.123456,
    "core": 0,
    "die": 0,
    "socket": 0,
    "node": 0,
    "thread": 1234,
    "counter-value": 1000000,
    "unit": "cycles",
    "event": "cpu-cycles",
    "variance": 0.01,
    "runtime": 1.0,
    "metric-value": 2.0,
    "metric-unit": "IPC"
}

此输出指示在节点 0 的套接字 0 上的 die 0 的核心 0 上的线程 1234 的 cpu-cycles 事件的计数器值为 1,000,000 周期,方差为 0.01,运行时间为 1.0 秒,并且 IPC (每周期指令数) 度量值为 2.0。

09-17 20:59