实践流程

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 内被发出(超标量)
  • $\text{uOps Per Cycle} = \frac{\text{Total uOps}}{\text{Total Cycles}}$
  • $\text{IPC} = \frac{\text{Instructions}}{\text{Total Cycles}}$
  • $\text{Block RThroughput} = \frac{\text{Total Cycles}}{\text{Iterations}}$ :一次迭代需要的周期数,通常在比较两份代码时,可以直接看 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 等等。

在下面那部分比较直观,展示的是每周期内多少条指令被分发。也就是说,只有 2. 2%的指令完整得利用了超标量单元。

调度器与资源使用

--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 NameMeaning (Execution Unit)Avg UsedMax UsedTotal Capacity
CyUnitBBranch unit (e.g., branch prediction, jumps)0124
CyUnitIInteger ALU (General-purpose integer execution)454848
CyUnitIDInteger Divide Unit0016
CyUnitIMInteger Multiply Unit0032
CyUnitISInteger Store/Shift Unit172024
CyUnitLSLoad/Store Unit (memory access)0028
CyUnitVVector Unit (SIMD/AVX)0048
CyUnitVCVector Compute Unit (General vector operations)0016
CyUnitVDVector Divide Unit0016
CyUnitVMVector Multiply Unit0032
退役控制单元

来源于 --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)。

    参考文献