工具链实践——llvm-mca
实践流程
https://llvm.org/docs/CommandGuide/llvm-mca.html
llvm-mca 是一款性能分析工具,它利用 LLVM 中可用的信息(例如调度模型)来静态测量特定 CPU 上机器码的性能。性能以吞吐量以及处理器资源消耗来衡量。此工具的主要目标不仅是预测代码在目标机器上运行时的性能,还包括帮助诊断潜在的性能问题。分析和报告风格借鉴了英特尔的 IACA 工具。
基本使用方法:把 clang foo.c -O2 --target=x86_64 -S -o - | llvm-mca -mcpu=btver2
汇编代码直接输入给 llvm-mca。调度模型不仅用于计算指令延迟和吞吐量,还用于了解可用的处理器资源以及如何模拟它们。根据设计,llvm-mca 进行的分析质量不可避免地受到 LLVM 中调度模型质量的影响。
特定选项
-dispatch=<width>
:为处理器指定不同的调度宽度-register-file-size=<size>
:此标志限制用于寄存器重命名目的的物理寄存器的数量(0 表示不限数量)-iterations=<number of iterations>
:指定要运行的迭代次数。-timeline
:启用时间线视图-scheduler-stats
:启用额外的调度程序统计信息。此视图收集并分析指令发出事件。默认情况下禁用此视图。-dispatch-stats
:启用额外的调度统计信息。此视图收集并分析指令调度事件,以及静态/动态调度停顿事件。默认情况下禁用此视图。-instruction-info
:启用指令信息视图。默认情况下启用此选项。-all-stats
: 打印所有硬件统计信息。这将启用与调度逻辑、硬件调度程序、寄存器文件和退休控制单元相关的额外统计信息。默认情况下禁用此选项。-all-views
:启用所有视图。--bottleneck-analysis
:自动化瓶颈分析。
原理
llvm-mca 以汇编代码(一般是代码片段!)作为输入。在现有 LLVM 目标汇编解析器的帮助下,汇编代码被解析为一系列 MCInst。然后,由 Pipeline
模块分析解析后的 MCInst 序列以生成性能报告。Pipeline 模块在迭代循环中模拟机器代码序列的执行(默认值为 100)。在此过程中,管道收集大量与执行相关的统计信息。在此过程结束时,管道生成并打印从收集的统计信息生成的报告。
LLVM-MCA 不考虑完整的 CPU 前端, 即没有取码和译码环节
基础实践
先书写一个简单的 C++函数 (GPT 写的):
#include <vector>
std::vector<int> findPrimes(int start, int end) {
std::vector<int> primes;
for (int i = start; i <= end; ++i) {
if (i > 1) {
bool is_prime = true;
for (int j = 2; j * j <= i; ++j) {
if (i % j == 0) {
is_prime = false;
break;
}
}
if (is_prime) {
primes.push_back(i);
}
}
}
return primes;
}
然后使用 llvm 工具链的 clang++先编译为汇编文件并将结果传给 llvm-mca:
clang++ -S -O3 test.cpp -o - | llvm-mca | head -10
head -10
是为了只打印头部的重要信息,我的 MacBookPro M3 Max内容如下:
Iterations: 100
Instructions: 19200
Total Cycles: 11355
Total uOps: 22800
Dispatch Width: 6
uOps Per Cycle: 2.01
IPC: 1.69
Block RThroughput: 54.5
他们分别表示:
- Iterations:仿真负载的模拟运行次数
- Instructions:指令数
- Cycles:总周期数
- uOps:微操作数量(例如 DIV 会由多个μOp 组成,尤其是 CISC 会拆分为多个类 RISC 的μOps)
- Dispatch Width:发射宽度,有多少个μOps 会在 1 个 cycle 内被发出(超标量)
- :一次迭代需要的周期数,通常在比较两份代码时,可以直接看
Block RThroughput
,这就是一般意义上的性能指标(谁跑得快)。
从上述分析我们其实已经可以粗略得知,IPC 远远没达到理想值,可能存在访存延迟、依赖和流水线停顿。
高级实践
现在我们用一个更简单的例子(为了防止打印太多内容)可以来详细查看一下完整版的输出内容,我们尽量一次性包含所有的命令行选项:
int compute(int a, int b) {
int x = a + b; // Data dependency 1
int y = x * 2; // Data dependency 2
if (y > 10) { // Control flow (if condition)
return y - 10;
}
return y;
}
clang++ -S -O3 test.cpp -o - | llvm-mca --timeline --instruction-info --all-stats --bottleneck-analysis --dispatch-stats --all-views --show-encoding > anaylsis.txt
这个例子就比较简单了,我们来拆分一下他打印的内容:
基础性能总结
Iterations: 100
Instructions: 600
Total Cycles: 503
Total uOps: 600
Dispatch Width: 6
uOps Per Cycle: 1.19
IPC: 1.19
Block RThroughput: 1.8
性能瓶颈分析
--bottleneck-analysis
在此起作用:
Cycles with backend pressure increase [ 89.86% ]
Throughput Bottlenecks:
Resource Pressure [ 0.00% ]
Data Dependencies: [ 89.86% ]
- Register Dependencies [ 89.86% ]
- Memory Dependencies [ 0.00% ]
Critical sequence based on the simulation:
Instruction Dependency Information
+----< 4. csel w0, w10, w9, gt
|
| < loop carried >
|
+----> 0. add w8, w1, w0 ## REGISTER dependency: w0
| 1. lsl w9, w8, #1
| 2. sub w10, w9, #10
+----> 3. cmp w8, #5 ## REGISTER dependency: w8
+----> 4. csel w0, w10, w9, gt ## REGISTER dependency: nzcv
| 5. ret
|
| < loop carried >
|
+----> 0. add w8, w1, w0 ## REGISTER dependency: w0
通过以上内容我们可以得知:
- 89.86%的周期有“后端压力”,意味着指令执行较慢,多数因为有依赖和资源导致的停顿
- 没有资源压力(0.00%),意味着所有执行单元还没被全部利用,即还是上述依赖关系导致的
- 数据依赖也是 89.86%,即所有压力都是寄存器的数据依赖导致的。
- 没有内存压力(0.00%),即 load/store 的访存操作对性能没有影响。
在上述这段关键依赖指令序列中,显然我们已经知道了注释里写的 REGISTER DEPENDENCY,即几乎每条指令都对上一条的某个寄存器存在依赖关系。
指令分析
--instruction-info
和 --show-encoding
在此起作用:
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[7]: Encoding Size
[1] [2] [3] [4] [5] [6] [7] Encodings: Instructions:
1 2 1.00 4 28 00 00 0b add w8, w1, w0
1 1 0.50 4 09 79 1f 53 lsl w9, w8, #1
1 1 0.25 4 2a 29 00 51 sub w10, w9, #10
1 1 0.25 4 1f 15 00 71 cmp w8, #5
1 1 0.25 4 40 c1 89 1a csel w0, w10, w9, gt
1 0 1.00 U 4 c0 03 5f d6 ret
这里也可以重点看[3],本质还是把基础性能总结的内容详细打了出来。
发射数据
--dispatch-stats
在此起作用:
Dynamic Dispatch Stall Cycles:
RAT - Register unavailable: 0
RCU - Retire tokens unavailable: 0
SCHEDQ - Scheduler full: 442 (87.9%)
LQ - Load queue full: 0
SQ - Store queue full: 0
GROUP - Static restrictions on the dispatch group: 0
USH - Uncategorised Structural Hazard: 0
Dispatch Logic - number of cycles where we saw N micro opcodes dispatched:
[# dispatched], [# cycles]
0, 138 (27.4%)
1, 176 (35.0%)
2, 177 (35.2%)
4, 1 (0.2%)
6, 11 (2.2%)
这一段分析了为什么 CPU 停顿以及每周期发射的微操作。
- RAT:Register Allocation Table,代表 CPU 用完了物理寄存器,因此推迟指令执行
- RCU (Retire Control Unit),太多指令准备退役,导致后端压力(建议学习保留站知识)
- SCHEDQ (Scheduler full),指令调度器已经满载,无法支持新的指令装入
- LQ (Load Queue full),超标量单元的“load缓冲”已满,无法支持更多加载指令。
- SQ:同上,存储指令
- GROUP (Static dispatch restrictions),CPU 在一起发射特定指令组合时受限
- USH (Uncategorized Structural Hazard),硬件单元的限制阻碍了指令的执行
主要瓶颈在于 Scheduler full,即 CPU 没能快速分发某些命令,导致后续必须等待。可能的解决措施可以是减少指令依赖、增加 ILP 等等。
在下面那部分比较直观,展示的是
调度器与资源使用
--all-stats
在此起作用:
Schedulers - number of cycles where we saw N micro opcodes issued:
[# issued], [# cycles]
0, 101 (20.1%)
1, 206 (41.0%)
2, 194 (38.6%)
3, 2 (0.4%)
Scheduler's queue usage:
[1] Resource name.
[2] Average number of used buffer entries.
[3] Maximum number of used buffer entries.
[4] Total number of buffer entries.
[1] [2] [3] [4]
CyUnitB 0 1 24
CyUnitI 45 48 48
CyUnitID 0 0 16
CyUnitIM 0 0 32
CyUnitIS 17 20 24
CyUnitLS 0 0 28
CyUnitV 0 0 48
CyUnitVC 0 0 16
CyUnitVD 0 0 16
CyUnitVM 0 0 32
上一部分是分发,这一块就是执行操作,代表指令的执行单元的使用频度。
下面这一块内容表示:超标量单元的并行执行单元的个数,可以看出 Mac 还是非常夸张。
Resource Name | Meaning (Execution Unit) | Avg Used | Max Used | Total Capacity |
---|---|---|---|---|
CyUnitB | Branch unit (e.g., branch prediction, jumps) | 0 | 1 | 24 |
CyUnitI | Integer ALU (General-purpose integer execution) | 45 | 48 | 48 |
CyUnitID | Integer Divide Unit | 0 | 0 | 16 |
CyUnitIM | Integer Multiply Unit | 0 | 0 | 32 |
CyUnitIS | Integer Store/Shift Unit | 17 | 20 | 24 |
CyUnitLS | Load/Store Unit (memory access) | 0 | 0 | 28 |
CyUnitV | Vector Unit (SIMD/AVX) | 0 | 0 | 48 |
CyUnitVC | Vector Compute Unit (General vector operations) | 0 | 0 | 16 |
CyUnitVD | Vector Divide Unit | 0 | 0 | 16 |
CyUnitVM | Vector Multiply Unit | 0 | 0 | 32 |
退役控制单元
来源于 --all-stats
:
这个模块作用是按序提交已经处理完的指令(这些指令没有错误!)
Retire Control Unit - number of cycles where we saw N instructions retired:
[# retired], [# cycles]
0, 103 (20.5%)
1, 200 (39.8%)
2, 200 (39.8%)
重排序缓冲利用情况(ROB)
来源于 --all-stats
:
Total ROB Entries: 192
Max Used ROB Entries: 61 (31.8%)
Average Used ROB Entries per cy: 56 (29.2%)
- 重排序缓冲存储的是已经乱序执行完毕的指令,他们正等待给 RCU 处理。
寄存器文件数据
来源于 --all-stats
:
Total number of mappings created: 500
Max number of mappings used: 51
- 第一条意味着存在(硬件)寄存器重命名的数量
- 最高有 51 个寄存器同时在使用
寄存器重命名(Register Renaming) 是现代 CPU 为了提高指令级并行性(ILP)和减少数据依赖而采用的一种硬件优化技术。在 乱序执行(Out-of-Order Execution, OoOE) 的 CPU 中,逻辑寄存器的数量有限,但CPU 实际上有更多的物理寄存器。寄存器重命名的核心思想是 将有限的逻辑寄存器映射到更丰富的物理寄存器,从而消除假依赖(False Dependency)。
资源使用情况
来源于 --resource-pressure
:
Resources:
[0.0] - CyUnitB
[0.1] - CyUnitB
[1] - CyUnitBR
[2.0] - CyUnitFloatDiv
[2.1] - CyUnitFloatDiv
[3.0] - CyUnitI
[3.1] - CyUnitI
[3.2] - CyUnitI
[3.3] - CyUnitI
[4] - CyUnitID
[5] - CyUnitIM
[6.0] - CyUnitIS
[6.1] - CyUnitIS
[7] - CyUnitIntDiv
[8.0] - CyUnitLS
[8.1] - CyUnitLS
[9.0] - CyUnitV
[9.1] - CyUnitV
[9.2] - CyUnitV
[10] - CyUnitVC
[11] - CyUnitVD
[12.0] - CyUnitVM
[12.1] - CyUnitVM
Resource pressure per iteration:
[0.0] [0.1] [1] [2.0] [2.1] [3.0] [3.1] [3.2] [3.3] [4] [5] [6.0] [6.1] [7] [8.0] [8.1] [9.0] [9.1] [9.2] [10] [11] [12.0] [12.1]
0.50 0.50 1.00 - - 1.97 1.53 1.96 1.54 - - 1.00 2.00 - - - - - - - - - -
Resource pressure by instruction:
[0.0] [0.1] [1] [2.0] [2.1] [3.0] [3.1] [3.2] [3.3] [4] [5] [6.0] [6.1] [7] [8.0] [8.1] [9.0] [9.1] [9.2] [10] [11] [12.0] [12.1] Instructions:
- - - - - 0.94 0.06 0.92 0.08 - - - 2.00 - - - - - - - - - - add w8, w1, w0
- - - - - 0.03 0.47 0.03 0.47 - - 1.00 - - - - - - - - - - - lsl w9, w8, #1
- - - - - 0.02 0.48 0.02 0.48 - - - - - - - - - - - - - - sub w10, w9, #10
- - - - - 0.47 0.03 0.47 0.03 - - - - - - - - - - - - - - cmp w8, #5
- - - - - 0.03 0.48 0.02 0.47 - - - - - - - - - - - - - - csel w0, w10, w9, gt
0.50 0.50 1.00 - - 0.48 0.01 0.50 0.01 - - - - - - - - - - - - - - ret
这一块内容其实也是之前的详细展示,可以直接意会即可,一般不会细致到每条指令的超标量单元分析。
时间线
来源于 --timeline
:
Timeline view:
0123456789 0123456789 012
Index 0123456789 0123456789 0123456789
[0,0] DeeER. . . . . . . . . . . add w8, w1, w0
[0,1] D==eER . . . . . . . . . . lsl w9, w8, #1
[0,2] D===eER . . . . . . . . . . sub w10, w9, #10
[0,3] D==eE-R . . . . . . . . . . cmp w8, #5
[0,4] D====eER . . . . . . . . . . csel w0, w10, w9, gt
[0,5] DE-----R . . . . . . . . . . ret
[1,0] .D====eeER. . . . . . . . . . add w8, w1, w0
[1,1] .D======eER . . . . . . . . . lsl w9, w8, #1
[1,2] .D=======eER . . . . . . . . . sub w10, w9, #10
[1,3] .D======eE-R . . . . . . . . . cmp w8, #5
[1,4] .D========eER . . . . . . . . . csel w0, w10, w9, gt
[1,5] .DE---------R . . . . . . . . . ret
[2,0] . D========eeER. . . . . . . . . add w8, w1, w0
[2,1] . D==========eER . . . . . . . . lsl w9, w8, #1
[2,2] . D===========eER . . . . . . . . sub w10, w9, #10
[2,3] . D==========eE-R . . . . . . . . cmp w8, #5
[2,4] . D============eER . . . . . . . . csel w0, w10, w9, gt
[2,5] . DE-------------R . . . . . . . . ret
[3,0] . D============eeER. . . . . . . . add w8, w1, w0
[3,1] . D==============eER . . . . . . . lsl w9, w8, #1
[3,2] . D===============eER . . . . . . . sub w10, w9, #10
[3,3] . D==============eE-R . . . . . . . cmp w8, #5
[3,4] . D================eER . . . . . . . csel w0, w10, w9, gt
[3,5] . DE-----------------R . . . . . . . ret
[4,0] . D================eeER. . . . . . . add w8, w1, w0
[4,1] . D==================eER . . . . . . lsl w9, w8, #1
[4,2] . D===================eER . . . . . . sub w10, w9, #10
[4,3] . D==================eE-R . . . . . . cmp w8, #5
[4,4] . D====================eER . . . . . . csel w0, w10, w9, gt
[4,5] . DE---------------------R . . . . . . ret
[5,0] . D====================eeER. . . . . . add w8, w1, w0
[5,1] . D======================eER . . . . . lsl w9, w8, #1
[5,2] . D=======================eER . . . . . sub w10, w9, #10
[5,3] . D======================eE-R . . . . . cmp w8, #5
[5,4] . D========================eER . . . . . csel w0, w10, w9, gt
[5,5] . DE-------------------------R . . . . . ret
[6,0] . .D========================eeER. . . . . add w8, w1, w0
[6,1] . .D==========================eER . . . . lsl w9, w8, #1
[6,2] . .D===========================eER . . . . sub w10, w9, #10
[6,3] . .D==========================eE-R . . . . cmp w8, #5
[6,4] . .D============================eER . . . . csel w0, w10, w9, gt
[6,5] . .DE-----------------------------R . . . . ret
[7,0] . . D============================eeER. . . . add w8, w1, w0
[7,1] . . D==============================eER . . . lsl w9, w8, #1
[7,2] . . D===============================eER . . . sub w10, w9, #10
[7,3] . . D==============================eE-R . . . cmp w8, #5
[7,4] . . D================================eER . . . csel w0, w10, w9, gt
[7,5] . . DE---------------------------------R . . . ret
[8,0] . . D================================eeER. . . add w8, w1, w0
[8,1] . . D==================================eER . . lsl w9, w8, #1
[8,2] . . D===================================eER . . sub w10, w9, #10
[8,3] . . D==================================eE-R . . cmp w8, #5
[8,4] . . D====================================eER . . csel w0, w10, w9, gt
[8,5] . . DE-------------------------------------R . . ret
[9,0] . . D====================================eeER. . add w8, w1, w0
[9,1] . . D======================================eER . lsl w9, w8, #1
[9,2] . . D=======================================eER. sub w10, w9, #10
[9,3] . . D======================================eE-R. cmp w8, #5
[9,4] . . D========================================eER csel w0, w10, w9, gt
[9,5] . . DE-----------------------------------------R ret
Average Wait times (based on the timeline view):
[0]: Executions
[1]: Average time spent waiting in a scheduler's queue
[2]: Average time spent waiting in a scheduler's queue while ready
[3]: Average time elapsed from WB until retire stage
[0] [1] [2] [3]
0. 10 19.0 0.1 0.0 add w8, w1, w0
1. 10 21.0 0.0 0.0 lsl w9, w8, #1
2. 10 22.0 0.0 0.0 sub w10, w9, #10
3. 10 21.0 0.0 1.0 cmp w8, #5
4. 10 23.0 0.0 0.0 csel w0, w10, w9, gt
5. 10 1.0 1.0 23.0 ret
10 17.8 0.2 4.0 <total>
这一块是最后一个重点,是模拟执行的流水时间线
D
: 表示分发指令(dispatch)。=
: 表示等待执行。e
: 表示执行指令(execute)。E
: 表示执行完成。-
: 表示等待退役。R
: 表示退役(retire)。