perf性能分析(7) -- Top-down 分析方法及VTune工具
现代性能分析,使用针对pipeline的分析办法(取代CPU cycles分析)。这源于现代CPU架构的复杂性。
现代CPU处理指令架构,分为前端 Front-end,后端 Back-end两部分。阻碍指令执行的因素,从硬件看,源于前端或后端的Stall。
1. CPU 流水线

Intel CPU流水线一般分为5级。其中解码(ID),意思是将指令操作分解为多个uOp(即拆分为多个更低级的硬件操作),如ADD eax, [mem1],可以拆分成两个微指令:从内存读取数据,再执行ADD操作。
2. CPU 架构及流水线的执行过程
执行过程:前端执行完IF -> ID之后,然后在一个名为allocation的过程中(下图中星标处),uOps被输送到后端。后端监控uOp的操作数(data operand)何时可用,并在可用的执行单元中执行uOps。
当uOp执行完成之后,称之为执行完成(retirement),且将uOp的结果被写会寄存器或者内存(经过Store Buffer写入内存)。
大多数uOps都会完全通过流水线并退出,但有些投机指令uOps可能会在退出前被取消–如预测错误的分支。

在Intel处理器中,一个core一般有四个执行端口,即每个cycle最多可以执行四个uOps。
在处理器架构中,有一个抽象概念:pipeline slot(流水线槽),用来表示用于执行一个uOp所需要的硬件资源。在每个时钟周期,有四个流水线槽可用,流水线槽可以是空的,也可以是被uOp填充。流水线槽在Allocation阶段(上图中的星号标记处),将uOp从前端分配到后端执行单元。
PMU监控流水线槽的状态,在每个时钟周期,衡量流水线槽的利用率(是否填充有uOp),并对流水线槽进行分类,确定是前端瓶颈还是后端瓶颈。
3. Top-down 分析方法
从性能分析的角度看,一条微指令在流水线中的性能指标可以分为:
- 退出(
Retiring) –Micro Sequencer(微指令调度器)可能会成为瓶颈,例如调度浮点指令。 - 分支预测错误(
Bad Speculation) – 分支预测错误,或者memory ordering violation(多核多线程共享数据情形),导致Machine Clears(清除流水线)。 - 前端瓶颈(
Front-End Bottleneck) - 后端瓶颈(
Back-End Bottleneck)
Top-down分析思想根据上述的流水执行阶段及过程,首先从Top-level分类分析步骤:

然后,继续细分(Breakdown),确定是哪个阶段中的哪个资源导致的stall:

相比以前基于事件的度量的方式,基于
Top-down分析方法,可以更准确地定位性能瓶颈的根源,指导开发者进行针对性的优化。
3.1. Frontend Bound – 前端瓶颈
前端主要职责为读取指令,解码之后,发送给后端。遇到分支指令,需要经过预测器预测下一个指令的地址,这意味着会出现由于分支预测错误并清除流水线导致的ICache Miss而引起前端阻塞。
而在取指/解码过程中,如果代码的局部性不好,由于ICache Miss,或者iTLB Miss,引起前端Stall。过高的miss率导致前端瓶颈。
L1/L2 Cache分为DCache和ICache。TLB也分为iTLB、dTLB和Second-Level TLB,地址从iTLB/dTLB中找不到则再从Second-Level TLB中查找。
3.2. Back-End Bound – 后端瓶颈
后端主要职责为执行指令,包括ALU、FPU、Memory等。后端瓶颈分为:
-
Core Bound- 除法指令过多,因为除法指令执行时间长;
-
Execution Port Utilzation过高,导致执行单元饱和; - 指令的数据依赖,即依赖上一个指令的结果,导致
Stall;
-
Memory Bound-
L1/L2/L3 Cache Miss引起的Stall,需要改进数据访问的局部性,或者减小数据的访问规模(如分块处理数据),或者False-Sharing引起的Cache Miss; -
Memory Bound:分为Memory BandWidth和Memory Latency,前者是指内存带宽不足,后者是指内存访问延迟过高;
-
3.3. 优化指令的处理能力(Throughput)
例如,改用向量指令(如AVX)。或者使用更高效的算法。
4. 一些优化手段
4.1. Frontend
- 减少代码的
footprint,如-fomit-frame-pointer - 调整代码布局:如是用
-fprofile-generate -fprofile-use - 调整代码布局:如用
__attribute__((hot)) - 分支预测优化:如
loop unrolling,特别是小的循环,如小于64次循环 - 分支预测优化:是用
if代替三目运算符;避免if-elses结构,switch-case排序
4.2. Back-End
- 减少
function call,如inline - 多线程避免
false-sharing,使用内存对齐 - gcc优化:如
__builtin_expect
4.3. 示例
#define likely(x) __builtin_expect(!!(x), 1) //gcc内置函数, 帮助编译器分支优化
#define unlikely(x) __builtin_expect(!!(x), 0)
if(likely(condition)) {
// 这里的代码执行的概率比较高
}
if(unlikely(condition)) {
// 这里的代码执行的概率比较高
}
#define CACHE_LINE __attribute__((aligned(64)))
struct S1 {
int r1;
int r2;
int r3;
S1(): r1(1),r2(2),r3(3){}
} CACHE_LINE;
5. Hazard 介绍
5.1. StructuralHazards – 结构性冲突
结构性冲突本质是CPU中硬件资源的竞争,比如流水线中,前后指令之间都需要经过译码器,访问内存,形成对译码器的争用。
5.2. DataHazards – 数据依赖
五级流水线:取指IF -> 解码ID -> 执行EX -> 内存访问MEM -> 写回WB。
Data Hazard是指后一条指令的操作数,依赖于前一条指令的结果。操作数依赖分为三种关系:
- 先写后读(
Write-after-Read) –Data Denpendency - 先读后写(
Read-after-Write) –Anti-Dependency - 写后写(
Write-after-Write) –Output Dependency
CPU处理Data Hazard办法有两种:
- 插入
NOP指令,流水线停顿(Pipeline Stall),或者叫流水线冒泡(Pipeline Bubbling)。 -
Operand Forwarding– 操作数转发。
Operand Forwarding:在第一条指令的执行阶段完成之后,直接将结果数据传输给到下一条指令的 ALU。然后,下一条指令不需要再插入两个 NOP 阶段,就可以继续正常走到执行阶段。这样的解决方案,我们就叫作操作数前推(Operand Forwarding),或者操作数旁路(Operand Bypassing)。其实更合适的名字应该叫操作数转发。这里的 Forward,其实就是我们写 Email 时的“转发”(Forward)的意思。

5.3. ControlHazards – 控制依赖
主要使用分支预测。
5.4. 流水线 – 乱序执行

更详细资料:cnblogs – 计算机组成原理——原理篇 处理器(中)
6. 使用VTune工具进行Top-down分析
对一个应用程序进行profile,一般首先使用Hotspots,获取测量得到的分类性能指标。以官方matrix_mul示例为例(以Windows为例,VTune自带样例的路径为C:\Users\Administrator\Documents\VTune\Samples):

然后,使用Microarchitecture Exploration,再次进行测试。测试完成之后,在Bottom-up页面查看Top-down Tree,获取更细粒度的性能指标:

- 下图为局部展开的
Front-End Bound、Back-End Bound。- 从图中看,
Front-End Bound为44.0%,即流水线槽(Pipeline Slot)由于前端没能及时提供指令而产生44.0%的空闲。Back-End Bound为100.0%,意味着后端由于内存带宽限制、指令的数据依赖(比如指令依赖上一条指令的计算结果)、L1/L2/L3缓存访问延迟严重,或者执行单元饱和,导致流水线停顿。- 综合前后端的瓶颈分析,前端应该是由于后端的瓶颈导致的。
6.1. Back-End Bound
在Top-down的顶层分类中,使用流水线槽(Pipeline-Slot)来测量瓶颈:Front-End Bound、Back-End Bound、Bad Speculation、Retiring。在深入到Back-End之后,使用PMU来测量后端的性能指标值,比如测量一个指令的时钟周期或者事件,进而度量其Stall来源并算比例值。其测量值,与使用流水线槽度量及计算得到的值会有差异。
另外,Back-End Bound又分为两个子类:Memory Bound与Core Bound,分别度量来自内存/Cache的Stall,以及来自Execution Port的Stall。
6.2. 微架构优化方法
在进行性能优化的时候,一般关注应用程序的顶层热点函数,即占用CPU最多的函数。VTune工具展示的profile结果也是热点 + 层次定位,比如Bottom-up页面,可以按照占用CPU时间排序查看,即查看热点函数以及其耗时占比。
按照最优优化顺序,首先优化程序算法,以及并行化,即优化大头。之后,再进行微架构性能profile,并按照Top-down方法,针对性的优化瓶颈。
在Top-down视图中,VTune会高亮显示Top-down分类中的瓶颈类型(如上面Bottom-up页面所示)。下表给出了不同分类应程序,其Top-down分类中各指标的合理范围,超出该范围5%则在VTune的profile结果页面中高亮显示:

- 这个阈值表是基于对Intel实验室中的一些工作负载的分析总结出来的,作为一般指导原则。
- 在Top-down分析过程中,没有高亮的指标,不需要花费时间优化,优化这一部分一般不会有太多的改善。
- 大多数未调优的应用程序都是后端瓶颈的。 解决后端问题通常与解决延迟源有关,延迟源会导致执行完成所需的时间超过必要时间。
6.3. 第三方优化示例
- https://github.com/yao-matrix/mblog/blob/main/mips.md
- https://weedge.github.io/perf-book-cn/zh/chapters/7-Overview-Of-Performance-Analysis-Tools/7-1_Intel_Vtune_cn.html
A. 资料
- 自顶向下的微架构分析方法:官方文档,介绍了
Top-down分析方法的原理和步骤。 - pdf – A Top-Down Method for Performance Analysis and Counters Architecture
- cnblogs –C/C++ 性能优化背后的方法论:TMAM
- 调优指南: Xeon E5 v3
- pdf – intel lectures: Intel_VTune_Amplifier-jackson:PPT文档
- github – pmu-tools
- User Interface Reference:VTune官方参考文档,该章节的子章节详细介绍了VTune的每个页面。
A.1. 更多阅读
Enjoy Reading This Article?
Here are some more articles you might like to read next: