0. 前言
在日常开发过程中,性能分析是一项常见任务。目前市面上存在多种性能分析方法和工具,例如在安卓平台使用Perfetto,Nvidia则提供Nsight工具用于GPU性能分析,软件分析中perf火焰图等。那么,是否存在专门针对CPU性能分析的工具和方法?答案是肯定的,这就是TopDown分析方法。
TopDown方法由Intel工程师提出,是一种软件性能分析技术。其核心思想包括:
- 分层分析:从系统级性能(如CPU使用率、上下文切换)逐步深入到硬件级事件(如缓存命中率、分支预测准确性)。
- 优先级排序:通过关键指标(如CPU利用率、内存带宽)快速定位主要瓶颈,再针对性分析细节。
大家平时分析性能时常用的可能是perf,那么TopDown方法与perf有什么区别呢?
首先,TopDown方法是一种分析方法论,强调从宏观到微观的层次化分析,通过识别系统瓶颈(如CPU利用率、内存带宽、缓存未命中率)逐步定位问题根源。而perf是一个具体的工具,通过性能监控单元(PMU)和tracepoints等机制,直接采集硬件事件(如CPU周期、缓存命中率、分支预测错误等)和软件事件(如函数调用栈、锁争用),并提供实时统计、采样和报告功能。
其次,TopDown方法在具体实现中通常复用perf工具读取PMU功能,但这并非唯一途径,也可以通过其他方式实现。
尽管网上有许多基于Intel平台的TopDown分析文章,但笔者主要关注的是Arm平台。本文将详细介绍Arm平台上的TopDown分析方法的具体实施方式,主要参考的是Arm官方提供的Telemetry Solution代码库
Arm的TopDown分析方法分为两个阶段:
- TopDown分析:通过管道延迟相关的指标检测和识别CPU性能瓶颈。
- 微架构探索:进一步分析瓶颈资源的CPU利用率
该方法基于标准化的遥测框架(Telemetry Specification),通过PMU(性能监控单元)采集数据,并利用Python脚本进行分析。提供了四个关键指标用于第一阶段分析:frontend_bound、backend_bound、bad_speculation和retiring。
这是TopDown原始论文的结构图:
这是Arm TopDown的工具中的结构图:
可以看到ARM CPU的TopDown分析结构与论文中的框架存在差异,图中包含大量信息,暂不详细说明,后续将逐一详细说明。
对于对CPU硬件了解较少的开发者而言,前端(Frontend)、后端(Backend)和PMU等概念可能不够清晰。在介绍TopDown分析方法前,会先简要介绍相关背景知识。如需深入了解,建议查阅专门教程或本系列其他文章。
1. 背景
要分析CPU的性能,首先要对CPU的硬件结构有个大概了解,需要知道一条指令从内存载入到CPU,再到运行,再到返回结果都需要经过哪些硬件单元,也就是说要了解CPU的Pipeline,然后需要测量哪些硬件单元执行慢,汇总统计信息,定位性能的瓶颈点,最后再去做软件的改进方案。 在背景介绍中,需要关注以下三个问题:
- CPU性能分析中需要关注哪些硬件器件? -- pipeline
- 如何观测这些硬件器件的运行情况? -- PMU events
- 如何用软件读取这些运行信息? -- perf
1.1 Pipeline
Pipeline的简单结构如下
根据硬件单元的位置与功能差异,可分为前端和后端两部分。
前端(Front End)
CPU的前端是一个按顺序执行的流水线,负责从I-Cache中获取指令并进行解码。解码后的指令会被分解为微操作,这些微操作会被排队并分发至后端执行引擎,根据其可用性进行分配。 关键硬件单元包括分支预测器,它通过预测分支方向及间接分支的目标地址,减少流水线中断和周期浪费,从而提升程序执行效率。
后端(Back End)
CPU的后端负责执行已分配至相关执行单元的微操作。执行单元涵盖分支单元、加载/存储单元以及算术单元(如高级向量引擎)。不同微架构中,执行单元的数量及单条指令的执行周期存在差异,因此指令执行的延迟和吞吐量会因具体实现而异。
指令执行完成后,结果需在解决依赖关系后按顺序提交。这一过程确保了数据的正确性和程序的稳定性。
1.2 PMU
了解硬件单元后,下一步需掌握如何探查其执行情况。我们需要明确硬件单元的执行速度、指令完成量及是否存在阻塞(stall)现象。为此需要使用性能监测单元(PMU)。
现代处理器集成了专门的性能监测单元(PMU),用于测量与硬件执行相关的各类事件。通过分析这些事件,可深入理解代码在不同微架构单元中的执行行为。PMU支持同时监测多个事件,并将其与软件执行关联,从而识别优化机会并评估工作负载是否充分利用了底层微架构。具体可监测的事件包括:已退休指令数、CPU运行周期、缓存访问次数及分支预测准确性等。
需要特别注意的是,不同CPU的PMU支持的事件数量存在差异,因此在使用时应根据具体CPU型号确定其支持的事件。
PMU硬件主要包括以下部分:
- 配置寄存器(PMU configuration registers):用于控制和选择事件。
- 事件计数器(PMU event counters):记录硬件事件的发生次数。
- 专用功能计数器(Dedicated function counters):针对特定功能进行统计。
每个CPU的PMU可用计数器数量是有限的。若在Perf命令中选择的事件数量超过可用计数器,内核会通过时间复用技术为每个事件分配计数机会。Perf会在运行结束后根据总时间和实际运行时间对计数值进行缩放。这意味着当发生多路复用和缩放时,所选PMU事件的计数值仅为估算值。
当每次选择的PMU事件数量超过可用计数器时,建议将相关事件放入同一组中。这样,多路复用仅发生在组间而非组内。为此,可在事件组内使用大括号{ }进行分隔。
要获取具体PMU计数器数据,需查阅对应CPU的TRM(技术参考手册)文档。例如,针对本次测试平台(8650手机)的x4 CPU,可通过TRM确认其支持31个PMU计数器。
Arm Cortex-X4 Core Technical Reference Manual
1.3 Perf
现在我们已掌握PMU硬件的基础,接下来需要通过软件读取这些硬件事件。无需重复造轮子,Linux平台有perf工具,Windows平台有Windows Performance Toolkit。本文主要关注Linux的perf工具。
perf是Linux内核内置的性能分析工具,随内核代码一同发布,支持对CPU、内存等硬件事件的实时监控。它通过PMU接口与硬件交互,可采集包括指令周期、缓存访问、分支预测等关键指标,帮助开发者精准定位性能瓶颈。
perf stat -e
perf是一款功能强大的工具,在TopDown分析方法中主要使用perf stat -e命令。该命令用于运行指定程序并收集性能计数器的统计信息,其中-e选项用于指定要测量的事件类型。用户可直接输入事件名称(如cycles、cache-references),也可通过事件ID(如r8164)进行指定。
下面是perf stat -e命令的使用示例。
# event name
perf stat -e cpu-cycles ./a.out
# event id
perf stat -e '{r8164,r24} ./a.out
下面是perf stat -e 的帮助文档。
NAME
perf-stat - Run a command and gather performance counter statistics
OPTIONS
-e, --event=
Select the PMU event. Selection can be:
• a symbolic event name (use perf list to list all events)
• a raw PMU event (eventsel+umask) in the form of rNNN where NNN is a hexadecimal event descriptor.
• a symbolic or raw PMU event followed by an optional colon and a list of event modifiers, e.g., cpu-cycles:p. See
the perf-list(1) man page for details on event modifiers.
• a symbolically formed event like pmu/param1=0x3,param2/ where param1 and param2 are defined as formats for the
PMU in /sys/bus/event_source/devices/<pmu>/format/*
'percore' is a event qualifier that sums up the event counts for both
hardware threads in a core. For example:
perf stat -A -a -e cpu/event,percore=1/,otherevent ...
• a symbolically formed event like pmu/config=M,config1=N,config2=K/ where M, N, K are numbers (in decimal, hex,
octal format). Acceptable values for each of config, config1 and config2 parameters are defined by corresponding
entries in /sys/bus/event_source/devices/<pmu>/format/*
Note that the last two syntaxes support prefix and glob matching in
the PMU name to simplify creation of events across multiple instances
of the same type of PMU in large systems (e.g. memory controller PMUs).
Multiple PMU instances are typical for uncore PMUs, so the prefix
'uncore_' is also ignored when performing this match.
1.4 perf driver
进一步深入探讨,我们知道操作系统是软件与硬件之间的桥梁,而perf在Linux操作系统中的接口由perf驱动程序(perf driver)实现。perf工具正是通过该驱动程序与硬件PMU进行交互,从而采集性能数据。
perf driver接口(qcom 8650手机):
- 接口路径
ls /sys/bus/event_source/devices/armv8_pmuv3
caps cpus events format perf_event_mux_interval_ms power subsystem type uevent
- 当前接口管理的CPU,这里是0-7,8核CPU
cat /sys/bus/event_source/devices/armv8_pmuv3/cpus
0-7
- 接口支持的事件格式,这里是两个字符表示一个事件
cat /sys/bus/event_source/devices/armv8_pmuv3/format/event
config:0-15
perf driver的核心代码位于 kernel/events/core.c中。有兴趣的读者可关注后续文章,其中将对相关内容进行详细分析。
1.5 权限
运行perf工具通常需要root权限,但Linux系统可以通过设置perf_event_paranoid参数,使非root用户也能使用perf工具。
perf_event_paranoid参数的含义如下:
-1:允许所有用户(包括非特权用户)访问几乎所有的性能事件,包括CPU事件、tracepoint和ftrace等。0:允许访问CPU特定的性能数据,但禁止访问原始tracepoint或ftrace功能。1:禁止非特权用户访问CPU事件(需root权限)。>= 2:进一步禁止内核级性能分析(如内核函数调用跟踪)。
echo -1 > /proc/sys/kernel/perf_event_paranoid
在Android系统中,SELinux可能导致perf工具运行失败,需临时关闭SELinux以解决问题。
setenforce 0
2. TopDown 初见
对于程序员而言,学习任何技术都会从一个简单的"Hello World"示例开始。那么,我们也可以通过一个类似的测试来入门。
在QCOM 8650手机上进行测试,我们使用taskset命令将进程限定在X4 CPU上运行,通过执行ls命令来验证。
taskset 80 ./topdown-tool --perf-path ~/bin/perf --cpu cortex-x4 -m Topdown_L1 ls
Stage 1 (Topdown metrics)
=========================
[Topdown Level 1]
Frontend Bound... 24.77% slots
Backend Bound.... 44.57% slots
Retiring......... 26.74% slots
Bad Speculation.. 3.92% slots
我们先进入TopDown分析的第一阶段,观察到最多的时间消耗集中在Backend Bound。接下来将对Backend Bound进行详细分解。
$ taskset 80 ./topdown-tool --perf-path ~/bin/perf --cpu cortex-x4 -m Topdown_Backend ls
LICENSE README.md perf.stat.txt pyproject.toml setup.cfg tests topdown-tool topdown_tool tox.ini
Stage 1 (Topdown metrics)
=========================
[Topdown Backend]
Backend Core Bound........ 80.76% cycles
Backend Core Rename Bound. 61.06% cycles
Backend Busy Bound........ 32.62% cycles
Backend Memory Bound...... 18.74% cycles
Backend Memory Cache Bound 79.69% cycles
Backend Cache L1D Bound... 13.10% cycles
Backend Cache L2D Bound... 87.37% cycles
Backend Memory TLB Bound.. 21.95% cycles
Backend Memory Store Bound 11.39% cycles
细心的读者可能发现,相同级别的Bound总和并非100%,这主要是由于脚本一次性采集了大量PMU事件类型,而PMU硬件通过复用计数器导致的误差。
由于 ls命令本身实现简单且运行时间较短,观察到的主要时间消耗集中在Core Bound,即执行单元的活动。
进一步分解执行操作:
taskset 80 ./topdown-tool --perf-path ~/bin/perf --cpu cortex-x4 -m Operation_Mix ls
Stage 2 (uarch metrics)
=======================
[Speculative Operation Mix]
Load Operations Percentage...................... 17.44% operations
Store Operations Percentage..................... 10.63% operations
Integer Operations Percentage................... 50.79% operations
Advanced SIMD Operations Percentage............. 0.33% operations
Floating Point Operations Percentage............ 0.00% operations
Barrier Operations Percentage................... 0.12% operations
Branch Operations Percentage.................... 19.88% operations
Crypto Operations Percentage.................... 0.00% operations
SVE Operations (Load/Store Inclusive) Percentage 0.00% operations
可以看到50%的时间消耗在整数运算占比(Integer Operations Percentage),由于ls命令的执行流程较为基础,未涉及复杂的数据处理或浮点运算,因此其性能瓶颈主要集中在整数运算的执行效率上。
3. 总结
本文简要介绍了TopDown性能分析方法,涵盖了分析所需的背景知识、工具使用及示例程序的实践。通过分析一个简单的ls命令,帮助读者初步理解TopDown分析的核心逻辑。后续文章将深入解析TopDown分析的各个子项,包括分阶段分析、关键指标拆解及优化策略,敬请期待。