这篇文章简单整理 LLVM-MCA 的使用思路。注意这只是个人的工具笔记,我关注这个工具的性能预测功能,而代码生成质量报告这些是直接忽略的。另外,如果只是想简单使用的话看几眼 --help
就行了,进一步了解完整的使用方法推荐直接看官方文档。
基本使用
你可以直接在网页上运行 LLVM-MCA(也可以对比 uiCA、IACA 等竞品),我这里使用的本地环境。
先任意挑选一个简单的示例代码 mca_test.cpp
(来源):
int branch(int mStepSize, int ticks, bool running) {
if (running) ticks += mStepSize;
int x = ticks * 3;
int y = ticks * x * 5;
int z = ticks * 7 * x * y;
return x + y + z;
}
编译器生成汇编后传递给 llvm-mca
,命令为:
clang++ -S -O2 mca_test.cpp -o - | llvm-mca
输出结果:
warning: found a return instruction in the input assembly sequence.
note: program counter updates are ignored.
Iterations: 100
Instructions: 1200
Total Cycles: 1604
Total uOps: 1500
Dispatch Width: 6
uOps Per Cycle: 0.94
IPC: 0.75
Block RThroughput: 2.5
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.25 testl %edx, %edx
1 1 0.50 cmovel %edx, %edi
1 1 0.25 addl %esi, %edi
2 2 0.25 leal (%rdi,%rdi,2), %ecx
1 3 1.00 imull %ecx, %edi
2 2 0.25 leal (%rdi,%rdi,4), %eax
1 3 1.00 imull %eax, %edi
2 2 0.25 leal (,%rdi,8), %edx
1 1 0.25 subl %edi, %edx
1 1 0.25 addl %ecx, %eax
1 1 0.25 addl %edx, %eax
1 5 0.50 U retq
Resources:
[0] - Zn3AGU0
[1] - Zn3AGU1
[2] - Zn3AGU2
[3] - Zn3ALU0
[4] - Zn3ALU1
[5] - Zn3ALU2
[6] - Zn3ALU3
[7] - Zn3BRU1
[8] - Zn3FPP0
[9] - Zn3FPP1
[10] - Zn3FPP2
[11] - Zn3FPP3
[12.0] - Zn3FPP45
[12.1] - Zn3FPP45
[13] - Zn3FPSt
[14.0] - Zn3LSU
[14.1] - Zn3LSU
[14.2] - Zn3LSU
[15.0] - Zn3Load
[15.1] - Zn3Load
[15.2] - Zn3Load
[16.0] - Zn3Store
[16.1] - Zn3Store
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12.0] [12.1] [13] [14.0] [14.1] [14.2] [15.0] [15.1] [15.2] [16.0] [16.1]
0.33 0.33 0.34 2.68 3.02 2.67 2.67 0.96 - - - - - - - 0.33 0.33 0.34 0.33 0.33 0.34 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12.0] [12.1] [13] [14.0] [14.1] [14.2] [15.0] [15.1] [15.2] [16.0] [16.1] Instructions:
- - - 0.01 0.32 0.33 0.34 - - - - - - - - - - - - - - - - testl %edx, %edx
- - - 0.02 - - 0.98 - - - - - - - - - - - - - - - - cmovel %edx, %edi
- - - 0.32 0.33 0.35 - - - - - - - - - - - - - - - - - addl %esi, %edi
- - - 0.33 0.35 0.32 - - - - - - - - - - - - - - - - - leal (%rdi,%rdi,2), %ecx
- - - - 1.00 - - - - - - - - - - - - - - - - - - imull %ecx, %edi
- - - 0.65 - 0.33 0.02 - - - - - - - - - - - - - - - - leal (%rdi,%rdi,4), %eax
- - - - 1.00 - - - - - - - - - - - - - - - - - - imull %eax, %edi
- - - 0.32 - 0.33 0.35 - - - - - - - - - - - - - - - - leal (,%rdi,8), %edx
- - - 0.33 - 0.35 0.32 - - - - - - - - - - - - - - - - subl %edi, %edx
- - - 0.33 - 0.34 0.33 - - - - - - - - - - - - - - - - addl %ecx, %eax
- - - 0.33 0.02 0.32 0.33 - - - - - - - - - - - - - - - - addl %edx, %eax
0.33 0.33 0.34 0.04 - - - 0.96 - - - - - - - 0.33 0.33 0.34 0.33 0.33 0.34 - - retq
关键指标
Iterations: 100
Instructions: 1200
Total Cycles: 1604
Total uOps: 1500
Dispatch Width: 6
uOps Per Cycle: 0.94
IPC: 0.75
Block RThroughput: 2.5
也许用户最关心的就是位于顶部的指标,这些是性能预测报告的总览。
Iterations
表示指令模拟迭代的次数,默认会重复执行 100 遍,可以使用 --iterations
调整参数。接下来三个 Instructions
、Total Cycles
、Total uOps
是字面意思的统计信息,表示总共有多少个指令、周期以及微指令。
Dispatch Width
表示流水线的宽度,LLVM-MCA 如果不指定 arch,那么有些参数是根据实机使用的 CPU 选择对应的调度模型,我在这里使用的是 ZEN3 架构的调度模型,默认宽度为 6,也可以使用 --dispatch
调整参数。接下来两个指标表示单指令的性能,除了 IPC 以外还可以精细到微指令层面 uOps Per Cycle
。最后一个 Block RThroughput
表示整个代码块(基本块)的吞吐量倒数,指标越低即整体吞吐量越高。
通常在比较两份代码时,可以直接看 Block RThroughput
,这就是一般意义上的性能指标(谁跑得快)。同时 IPC
或者 uOps Per Cycle
的参考价值就没那么大,因为可以做到单指令效率低但是块整体效率更高(比如指令数减少了)。也可通过计算 Dispatch Width - uOps Per Cycle
的差值了解流水线的空置程度,大致意思是理想状态下还有多少的优化空间。
// -O0
Iterations: 100
Instructions: 2700
Total Cycles: 2004
Total uOps: 2700
Dispatch Width: 6
uOps Per Cycle: 1.35
IPC: 1.35
Block RThroughput: 10.0
作为对比,上面是 -O0
优化等级代码的分析报告,而我们示例用的是 -O2
,谁跑得快一目了然。
指令分析
Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)
[1] [2] [3] [4] [5] [6] Instructions:
1 1 0.25 testl %edx, %edx
1 1 0.50 cmovel %edx, %edi
1 1 0.25 addl %esi, %edi
2 2 0.25 leal (%rdi,%rdi,2), %ecx
1 3 1.00 imull %ecx, %edi
2 2 0.25 leal (%rdi,%rdi,4), %eax
1 3 1.00 imull %eax, %edi
2 2 0.25 leal (,%rdi,8), %edx
1 1 0.25 subl %edi, %edx
1 1 0.25 addl %ecx, %eax
1 1 0.25 addl %edx, %eax
1 5 0.50 U retq
这里会把前面的概括信息进一步展示,[4]
、[5]
、[6]
对于 LLVM-MCA 来说都是不确定因素,因为 LLVM-MCA 不能感知到精确的内存和缓存信息。这是提示预测可能不准确(打星号 *
)的意思。
另外,这里用的示例代码是比较偷懒的。LLVM-MCA 其实对于 call 和 ret 指令是直接报出警告的,基本无法分析。因此像这样的静态分析工具都是用来看一小段代码的。
资源压力
Resources:
[0] - Zn3AGU0
[1] - Zn3AGU1
[2] - Zn3AGU2
[3] - Zn3ALU0
[4] - Zn3ALU1
[5] - Zn3ALU2
[6] - Zn3ALU3
[7] - Zn3BRU1
[8] - Zn3FPP0
[9] - Zn3FPP1
[10] - Zn3FPP2
[11] - Zn3FPP3
[12.0] - Zn3FPP45
[12.1] - Zn3FPP45
[13] - Zn3FPSt
[14.0] - Zn3LSU
[14.1] - Zn3LSU
[14.2] - Zn3LSU
[15.0] - Zn3Load
[15.1] - Zn3Load
[15.2] - Zn3Load
[16.0] - Zn3Store
[16.1] - Zn3Store
Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12.0] [12.1] [13] [14.0] [14.1] [14.2] [15.0] [15.1] [15.2] [16.0] [16.1]
0.33 0.33 0.34 2.68 3.02 2.67 2.67 0.96 - - - - - - - 0.33 0.33 0.34 0.33 0.33 0.34 - -
Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12.0] [12.1] [13] [14.0] [14.1] [14.2] [15.0] [15.1] [15.2] [16.0] [16.1] Instructions:
- - - 0.01 0.32 0.33 0.34 - - - - - - - - - - - - - - - - testl %edx, %edx
- - - 0.02 - - 0.98 - - - - - - - - - - - - - - - - cmovel %edx, %edi
- - - 0.32 0.33 0.35 - - - - - - - - - - - - - - - - - addl %esi, %edi
- - - 0.33 0.35 0.32 - - - - - - - - - - - - - - - - - leal (%rdi,%rdi,2), %ecx
- - - - 1.00 - - - - - - - - - - - - - - - - - - imull %ecx, %edi
- - - 0.65 - 0.33 0.02 - - - - - - - - - - - - - - - - leal (%rdi,%rdi,4), %eax
- - - - 1.00 - - - - - - - - - - - - - - - - - - imull %eax, %edi
- - - 0.32 - 0.33 0.35 - - - - - - - - - - - - - - - - leal (,%rdi,8), %edx
- - - 0.33 - 0.35 0.32 - - - - - - - - - - - - - - - - subl %edi, %edx
- - - 0.33 - 0.34 0.33 - - - - - - - - - - - - - - - - addl %ecx, %eax
- - - 0.33 0.02 0.32 0.33 - - - - - - - - - - - - - - - - addl %edx, %eax
0.33 0.33 0.34 0.04 - - - 0.96 - - - - - - - 0.33 0.33 0.34 0.33 0.33 0.34 - - retq
省流:资源压力只要是数值分布均匀,那就是正常的。
这些指令(除了 ret 以外)都是在 ALU 上竞争资源,但数值也没高到哪里去(文档提到是基于 RR 进行分摊的),合理。
时间线
使用 llvm-mca --timeline
可以分析指令的调度情况。从而推断是数据依赖瓶颈还是硬件资源瓶颈。
Timeline view:
0123456789 0123456789 0123456789 0123456789
Index 0123456789 0123456789 0123456789 0123456789
[0,0] DeER . . . . . . . . . . . . . . . . testl %edx, %edx
[0,1] D=eER. . . . . . . . . . . . . . . . cmovel %edx, %edi
[0,2] D==eER . . . . . . . . . . . . . . . addl %esi, %edi
[0,3] D===eeER . . . . . . . . . . . . . . . leal (%rdi,%rdi,2), %ecx
[0,4] D=====eeeER . . . . . . . . . . . . . . imull %ecx, %edi
[0,5] .D=======eeER . . . . . . . . . . . . . . leal (%rdi,%rdi,4), %eax
[0,6] .D=========eeeER . . . . . . . . . . . . . imull %eax, %edi
[0,7] .D============eeER . . . . . . . . . . . . . leal (,%rdi,8), %edx
[0,8] .D==============eER . . . . . . . . . . . . . subl %edi, %edx
[0,9] . D========eE-----R . . . . . . . . . . . . . addl %ecx, %eax
[0,10] . D==============eER. . . . . . . . . . . . . addl %edx, %eax
[0,11] . DeeeeeE----------R. . . . . . . . . . . . . retq
[1,0] . D==============eER. . . . . . . . . . . . . testl %edx, %edx
[1,1] . D===============eER . . . . . . . . . . . . cmovel %edx, %edi
[1,2] . D================eER . . . . . . . . . . . . addl %esi, %edi
[1,3] . D================eeER . . . . . . . . . . . . leal (%rdi,%rdi,2), %ecx
[1,4] . D==================eeeER . . . . . . . . . . . imull %ecx, %edi
[1,5] . D=====================eeER . . . . . . . . . . . leal (%rdi,%rdi,4), %eax
[1,6] . D=======================eeeER . . . . . . . . . . imull %eax, %edi
[1,7] . D=========================eeER . . . . . . . . . . leal (,%rdi,8), %edx
[1,8] . D===========================eER. . . . . . . . . . subl %edi, %edx
[1,9] . D======================eE-----R. . . . . . . . . . addl %ecx, %eax
[1,10] . D============================eER . . . . . . . . . addl %edx, %eax
[1,11] . DeeeeeE------------------------R . . . . . . . . . retq
[2,0] . D===========================eER . . . . . . . . . testl %edx, %edx
[2,1] . D============================eER . . . . . . . . . cmovel %edx, %edi
[2,2] . D=============================eER . . . . . . . . . addl %esi, %edi
[2,3] . D==============================eeER. . . . . . . . . leal (%rdi,%rdi,2), %ecx
[2,4] . D================================eeeER . . . . . . . . imull %ecx, %edi
[2,5] . .D==================================eeER. . . . . . . . leal (%rdi,%rdi,4), %eax
[2,6] . .D====================================eeeER . . . . . . . imull %eax, %edi
[2,7] . .D=======================================eeER. . . . . . . leal (,%rdi,8), %edx
[2,8] . .D=========================================eER . . . . . . subl %edi, %edx
[2,9] . . D===================================eE-----R . . . . . . addl %ecx, %eax
[2,10] . . D=========================================eER . . . . . . addl %edx, %eax
[2,11] . . DeeeeeE-------------------------------------R . . . . . . retq
[3,0] . . D=========================================eER . . . . . . testl %edx, %edx
[3,1] . . D==========================================eER . . . . . . cmovel %edx, %edi
[3,2] . . D===========================================eER . . . . . . addl %esi, %edi
[3,3] . . D===========================================eeER . . . . . leal (%rdi,%rdi,2), %ecx
[3,4] . . D=============================================eeeER . . . . . imull %ecx, %edi
[3,5] . . D================================================eeER . . . . leal (%rdi,%rdi,4), %eax
[3,6] . . D==================================================eeeER . . . . imull %eax, %edi
[3,7] . . D====================================================eeER . . . leal (,%rdi,8), %edx
[3,8] . . D======================================================eER . . . subl %edi, %edx
[3,9] . . D=================================================eE-----R . . . addl %ecx, %eax
[3,10] . . D=======================================================eER . . . addl %edx, %eax
[3,11] . . DeeeeeE---------------------------------------------------R . . . retq
[4,0] . . D======================================================eER . . . testl %edx, %edx
[4,1] . . D=======================================================eER . . . cmovel %edx, %edi
[4,2] . . D========================================================eER. . . addl %esi, %edi
[4,3] . . D=========================================================eeER . . leal (%rdi,%rdi,2), %ecx
[4,4] . . D===========================================================eeeER. . imull %ecx, %edi
[4,5] . . .D=============================================================eeER . leal (%rdi,%rdi,4), %eax
[4,6] . . .D===============================================================eeeER imull %eax, %edi
Truncated display due to cycle limit
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 62.0 0.1 0.0 testl %edx, %edx
1. 10 63.0 0.0 0.0 cmovel %edx, %edi
2. 10 64.0 0.0 0.0 addl %esi, %edi
3. 10 64.5 0.0 0.0 leal (%rdi,%rdi,2), %ecx
4. 10 66.5 0.0 0.0 imull %ecx, %edi
5. 10 69.0 0.0 0.0 leal (%rdi,%rdi,4), %eax
6. 10 71.0 0.0 0.0 imull %eax, %edi
7. 10 73.5 0.0 0.0 leal (,%rdi,8), %edx
8. 10 75.5 0.0 0.0 subl %edi, %edx
9. 10 70.0 0.0 5.0 addl %ecx, %eax
10. 10 76.0 0.0 0.0 addl %edx, %eax
11. 10 1.0 1.0 71.0 retq
10 63.0 0.1 6.3 <total>
Index
是一对的,比如 [1, 2]
就表示第 1 次迭代和第 2 条指令(首次从 0 算起)。
接下来的一串字符就是具体的调度执行:
D
: 表示分发指令(dispatch)。=
: 表示等待执行。e
: 表示执行指令(execute)。E
: 表示执行完成。-
: 表示等待退役。R
: 表示退役(retire)。
NOTE: LLVM-MCA 不考虑完整的 CPU 前端(应该是很难做到?),所以没有取码(fetch)和译码(decode)过程的模拟。
很显然,LLVM-MCA 是支持乱序执行的,比如 [0, 9]
的执行开始时机是比 [0, 8]
还要早。但是仍需确保顺序退役(program order),所以需要等待 [0, 8]
退役。
另外提下分发指令的条件。看着比较复杂,依赖于调度模型。文档提到具体的分发需要满足四个条件:
- 分发组(dispatch group,大致意思是并行分发的多个微指令)大小要满足流水线宽度的限制。
- 重排序缓冲(ROB)中有足够的条目。
- 物理寄存器有足够的数量做寄存器重命名(不退役会一直占着物理寄存器)。
- 调度器未达到满容量。(Each processor scheduler implements a buffer of instructions.)
具体可以观察 --dispatch-stats
,解释比较长就折叠了:
再往下就是等待时间的分析。[0]
指的是指令的执行次数,注意虽然前面提到迭代是默认 100 次,但是 timeline 有其它调整参数限制了时钟周期总数,所以这里限制为只有 10 次迭代。
[1]
、[2]
和 [3]
可以依靠上面的调度情况算出来。简单来说:
[1]
表示队列的等待时间,计算方式为D
加上=
的次数的平均值。[2]
表示已就绪(ready)后的队列的等待时间,计算方式为 ready 到首个e
的时间差的平均值。[3]
表示回写到退役时间,计算方式为E
加上-
的次数的平均值。
NOTE: ready 状态在上表是看不出来的。ready 状态的指令可以出现在 =
的任意时间,也可以是与首个 e
(表示 issued)重叠。前者可能出现的情况可能是调度(比如硬件 port)资源暂时不够用。如果真的想看 ready 下标,也可以通过 --json
选项打印出来。
[1] - [2]
的差值可以体现指令对数据依赖的程度,[2]
本身可以体现硬件资源压力的程度,[3]
应该说明不了什么,可能是表达一种乱序的程度(这是我个人觉得,因为只有乱序程度足够高,执行时机足够提前,才能使得回写和退役的时间差拉得足够开)。
很显然在这里硬件资源是没什么压力的,相比之下数据依赖就是重灾区。
瓶颈分析
使用 llvm-mca --bottleneck-analysis
可以帮你自动化分析上述报告。
Cycles with backend pressure increase [ 87.09% ]
Throughput Bottlenecks:
Resource Pressure [ 0.00% ]
Data Dependencies: [ 87.09% ]
- Register Dependencies [ 87.09% ]
- Memory Dependencies [ 0.00% ]
Critical sequence based on the simulation:
Instruction Dependency Information
+----< 8. subl %edi, %edx
|
| < loop carried >
|
+----> 0. testl %edx, %edx ## REGISTER dependency: %edx
+----> 1. cmovel %edx, %edi ## REGISTER dependency: %flags
+----> 2. addl %esi, %edi ## REGISTER dependency: %edi
+----> 3. leal (%rdi,%rdi,2), %ecx ## REGISTER dependency: %edi
+----> 4. imull %ecx, %edi ## REGISTER dependency: %ecx
+----> 5. leal (%rdi,%rdi,4), %eax ## REGISTER dependency: %edi
+----> 6. imull %eax, %edi ## REGISTER dependency: %eax
| 7. leal (,%rdi,8), %edx
+----> 8. subl %edi, %edx ## REGISTER dependency: %edi
| 9. addl %ecx, %eax
+----> 10. addl %edx, %eax ## REGISTER dependency: %edx
11. retq
这里很直接告诉你是数据依赖造成的压力,并且是前后依赖寄存器中的数据导致的。
毕竟示例代码本身就是就是一种强数据依赖的算法。
剩下的吃个饭再写。保证尽量不鸽!