llvm-mca命令详解

概要

llvm-mca [options] [input]

描述

llvm-mca 是一种性能分析工具,它使用LLVM中可用的信息(例如调度模型)来静态测量特定CPU中机器代码的性能。

性能是根据吞吐量和处理器资源消耗来衡量的。该工具目前适用于具有无序后端的处理器,LLVM中提供了一种调度模型。

此工具的主要目标不仅是在目标上运行时预测代码的性能,还有助于诊断潜在的性能问题。

给定汇编代码序列,llvm-mca估计每周期指令(IPC)以及硬件资源压力。分析和报告风格的灵感来自英特尔的IACA工具。

例如,您可以使用clang,输出程序集编译代码,并将其直接导入llvm-mca进行分析:

$ clang foo.c -O2 -target x86_64-unknown-unknown -S -o - | llvm-mca -mcpu=btver2

或者对于Intel语法:

$ clang foo.c -O2 -target x86_64-unknown-unknown -mllvm -x86-asm-syntax=intel -S -o - | llvm-mca -mcpu=btver2

选项

如果input-或省略,则llvm-mca从标准输入读取。否则,它将从指定的文件名中读取。

如果省略-o选项,那么如果输入来自标准输入,则llvm-mca将其输出发送到标准输出。如果-o选项指定“ -”,则输出也将发送到标准输出。

选项 说明
-help 打印命令行选项的摘要
-mtriple=<target triple> 指定目标三重字符串
-march=<arch> 指定要分析代码的体系结构。它默认为主机默认目标
-mcpu=<cpuname> 指定要分析代码的处理器。默认情况下,cpu名称是从主机自动检测的
-output-asm-variant=<variant id> 为工具生成的报告指定输出程序集变体。在x86上,可能的值为[0,1]。此标志的值为0(vic.1)将为分析报告中的工具打印的代码启用AT&T(vic.Intel)汇编格式。
-dispath=<width> 为处理器指定不同的分派宽度。调度宽度默认为处理器调度模型中的“IssueWidth”字段。如果width为零,则使用默认的调度宽度。
-register-file-size=<size> 指定寄存器文件的大小。指定时,此标志限制可用于寄存器重命名的物理寄存器的数量。该标志的值为零意味着“无限数量的物理寄存器”
-iterations=<n> 指定要运行的迭代次数。如果此标志设置为0,则该工具将迭代次数设置为默认值(即100)。
-noalias=<bool> 如果设置,则该工具假定加载和存储不是别名。这是默认行为
-lqueue=<load queue size> 指定工具模拟的加载/存储单元中的加载队列的大小。默认情况下,该工具假定加载队列中的未绑定条目数。忽略此标志的值为零,而是使用默认的加载队列大小
-squeue=<store queue size> 指定工具模拟的加载/存储单元中存储队列的大小。默认情况下,该工具假定存储队列中的未绑定条目数。忽略此标志的值为零,而是使用默认的存储队列大小
-timeline 启用时间轴视图
-timeline-max-iterations=<iterations> 限制在时间线视图中打印的迭代次数。默认情况下,时间轴视图最多可打印10次迭代的信息。
-timeline-max-cycles=<cycles> 限制时间线视图中的周期数。默认情况下,循环次数设置为80。
-resource-pressure 启用资源压力视图。默认情况下启用此选项。
-register-file-stats 启用注册文件使用情况统计
-dispatch-stats 启用额外的调度统计信息。此视图收集并分析指令调度事件以及静态/动态调度停止事件。默认情况下禁用此视图
-scheduler-stats 启用额外的调度程序统计信 该视图收集并分析指令发布事件。默认情况下禁用此视图。
-instruction-info 启用指令信息视图。默认情况下启用此选项
-all-stats 打印所有硬件统计信息 这实现了与调度逻辑,硬件调度器,寄存器文件和退出控制单元相关的额外统计。默认情况下禁用此选项
-all-views 启用所有视图
-instruction-tables 基于处理器模型中可用的静态信息打印资源压力信息。这与资源压力视图不同,因为它不需要模拟代码。相反,它按顺序打印每条指令的资源压力的理论均匀分布
-bottleneck-analysis 打印有关影响吞吐量的瓶颈的信息。此分析可能很昂贵,默认情况下禁用。摘要视图中突出显示瓶颈

退出状态

llvm-mca成功返回0。否则,将向标准错误打印错误消息,并且工具返回1

使用标记分析特定代码块

llvm-mca允许可选地使用特殊代码注释来标记要分析的汇编代码的区域。以substring LLVM-MCA-BEGIN开头的注释标记代码区域的开头。以substring开头的注释LLVM-MCA-END标记代码区域的结尾。例如:

# LLVM-MCA-BEGIN
...
# LLVM-MCA-END

如果未指定用户定义的区域,则llvm-mca将采用包含输入文件中每条指令的默认区域。每个区域都是单独分析的,最终的性能报告是为每个代码区域生成的所有报告的并集。

代码区域可以有名称。例如:

# LLVM-MCA-BEGIN A simple example
add %eax, %eax
# LLVM-MCA-END

上面示例中的代码定义了一个名为“一个简单示例”的区域,其中包含一条指令。请注意如何在LLVM-MCA-END指令中重复区域名称。在没有重叠区域的情况下,匿名LLVM-MCA-END指令始终结束当前活动的用户定义区域。

嵌套区域的示例:

# LLVM-MCA-BEGIN foo
add %eax, %edx
# LLVM-MCA-BEGIN bar
sub %eax, %edx
# LLVM-MCA-END bar
# LLVM-MCA-END foo

重叠区域的示例:

# LLVM-MCA-BEGIN foo
add %eax, %edx
# LLVM-MCA-BEGIN bar
sub %eax, %edx
# LLVM-MCA-END foo
add %eax, %edx
# LLVM-MCA-END bar

请注意,多个匿名区域不能重叠。此外,重叠区域不能具有相同的名称。

可以从源代码中使用内联汇编指令来批注汇编文本:

int foo(int a, int b) {
__asm volatile("# LLVM-MCA-BEGIN foo");
a += 42;
__asm volatile("# LLVM-MCA-END");
a *= b;
return a;
}

LLVM-MCA是如何工作的

llvm-mca将汇编代码作为输入。在现有LLVM目标程序集解析器的帮助下,汇编代码被解析为MCInst序列。然后由Pipeline模块分析解析的MCInst序列以生成性能报告。

Pipeline模块在迭代循环中模拟机器代码序列的执行(默认值为100)。在此过程中,管道收集许多与执行相关的统计信息。在此过程结束时,管道会根据收集的统计信息生成并打印报告。

以下是该工具为四个元素的两个打包浮点向量的点积生成的性能报告示例。对目标x86,cpu btver2进行分析。使用位于以下位置的示例,可以通过以下命令生成以下结果 test/tools/llvm-mca/X86/BtVer2/dot-product.s:

$ llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=300 dot-product.s

Iterations: 300
Instructions: 900
Total Cycles: 610
Total uOps: 900

Dispatch Width: 2
uOps Per Cycle: 1.48
IPC: 1.48
Block RThroughput: 2.0


Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)

[1] [2] [3] [4] [5] [6] Instructions:
1 2 1.00 vmulps %xmm0, %xmm1, %xmm2
1 3 1.00 vhaddps %xmm2, %xmm2, %xmm3
1 3 1.00 vhaddps %xmm3, %xmm3, %xmm4


Resources:
[0] - JALU0
[1] - JALU1
[2] - JDiv
[3] - JFPA
[4] - JFPM
[5] - JFPU0
[6] - JFPU1
[7] - JLAGU
[8] - JMul
[9] - JSAGU
[10] - JSTC
[11] - JVALU0
[12] - JVALU1
[13] - JVIMUL

Resource pressure per iteration:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]
- - - 2.00 1.00 2.00 1.00 - - - - - - -

Resource pressure by instruction:
[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] Instructions:
- - - - 1.00 - 1.00 - - - - - - - vmulps %xmm0, %xmm1, %xmm2
- - - 1.00 - 1.00 - - - - - - - - vhaddps %xmm2, %xmm2, %xmm3
- - - 1.00 - 1.00 - - - - - - - - vhaddps %xmm3, %xmm3, %xmm4

根据这份报告,点积内核已经执行了300次,共计900条模拟指令。模拟微操作码(uOps)的总数也是900。

该报告分为三个主要部分。第一部分收集了一些性能数字; 本节的目标是快速概述性能吞吐量。重要的性能指标是 IPC,uOps Per Cycle和 Block RThroughput(Block Reciprocal Throughput)。

计算IPC将模拟指令的总数除以总循环数。在没有循环携带数据依赖性的情况下,观察到的IPC倾向于理论最大值,其可以通过将单个迭代的指令数除以块RT吞吐量来计算。

计算字段’uOps Per Cycle’,将模拟微操作码的总数除以总循环数。Dispatch Width与此字段之间的差异是性能问题的指标。在没有循环携带数据依赖性的情况下,观察到的“每循环uOps”应倾向于理论上的最大吞吐量,其可以通过将单次迭代的uOps数除以Block RThroughput来计算。

字段uOps每周期由上方的调度宽度限制。这是因为调度宽度限制了调度组的最大大小。IPC和’uOps Per Cycle’都受到硬件并行度的限制。硬件资源的可用性影响资源压力分布,并且它限制了每个周期可以并行执行的指令的数量。Dispatch Width与每个周期的理论最大uOps之间的差值(通过将单个迭代的uOps数除以Block RTrhoughput计算 得出)是由于缺少硬件资源而导致的性能瓶颈的指标。通常,Block RThroughput越低越好。

在这个例子中,是1.50。由于没有循环携带依赖性,当迭代次数趋于无穷大时,观察到的每循环uOps预计接近1.50。Dispatch Width(2.00)与理论最大uOp吞吐量(1.50)之间的差异是缺乏硬件资源导致性能瓶颈的指标,资源压力视图可帮助识别有问题的资源使用情况。uOps per iteration/Block RThroughput

报告的第二部分显示了序列中每条指令的延迟和相应吞吐量。该部分还报告了与微操作码数和操作码属性相关的额外信息(即’MayLoad’,’MayStore’和’HasSideEffects’)。

第三部分是资源压力视图。此视图报告每次迭代消耗的资源周期的平均数量,其中包含目标上可用的每个处理器资源单元的指令。信息由两个表组成。第一个表报告每次迭代平均花费的资源周期数。第二个表将资源周期与序列中的机器指令相关联。例如,指令vmulps的每次迭代总是在资源单元[6](JFPU1-浮点流水线#1)上执行,每次迭代平均消耗1个资源周期。请注意,在AMD Jaguar上,向量浮点乘法只能发布到管道JFPU1,而水平浮点加法只能发布到管道JFPU0。

资源压力视图有助于识别由特定硬件资源的高使用率引起的瓶颈。一般而言,应避免资源压力主要集中在少数资源上的情况。理想情况下,压力应均匀分布在多个资源之间。

时间线视图

时间轴视图通过指令管道生成每条指令状态转换的详细报告。该视图由命令行选项启用-timeline。当指令转换到管道的各个阶段时,它们的状态将在视图报告中描述。这些状态由以下字符表示:

  • -D:发送指令。
  • e:正在执行指令。
  • E:已经执行指令。
  • R:指令已退役。
  • =:指令已经发送,等待执行。
  • -:执行指令,等待退役。

下面是使用以下命令在llvm-mca中定位test/tools/llvm-mca/X86/BtVer2/dot-product.s和处理 的点积示例子集的时间线视图 :

$ llvm-mca -mtriple=x86_64-unknown-unknown -mcpu=btver2 -iterations=3 -timeline dot-product.s

Timeline view:
012345
Index 0123456789

[0,0] DeeER. . . vmulps %xmm0, %xmm1, %xmm2
[0,1] D==eeeER . . vhaddps %xmm2, %xmm2, %xmm3
[0,2] .D====eeeER . vhaddps %xmm3, %xmm3, %xmm4
[1,0] .DeeE-----R . vmulps %xmm0, %xmm1, %xmm2
[1,1] . D=eeeE---R . vhaddps %xmm2, %xmm2, %xmm3
[1,2] . D====eeeER . vhaddps %xmm3, %xmm3, %xmm4
[2,0] . DeeE-----R . vmulps %xmm0, %xmm1, %xmm2
[2,1] . D====eeeER . vhaddps %xmm2, %xmm2, %xmm3
[2,2] . D======eeeER vhaddps %xmm3, %xmm3, %xmm4


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. 3 1.0 1.0 3.3 vmulps %xmm0, %xmm1, %xmm2
1. 3 3.3 0.7 1.0 vhaddps %xmm2, %xmm2, %xmm3
2. 3 5.7 0.0 0.0 vhaddps %xmm3, %xmm3, %xmm4

时间轴视图很有趣,因为它显示了执行期间的指令状态更改。它还介绍了该工具如何处理在目标上执行的指令,以及如何计算其计时信息。

时间轴视图由两个表组成。第一个表格显示了随时间改变状态的指令(以周期测量); 第二个表(命名为“ 平均等待时间”)报告有用的时序统计信息,这有助于诊断由长数据依赖性和硬件资源的次优使用导致的性能瓶颈。

时间线视图中的指令由一对索引标识,其中第一索引标识迭代,第二索引是指令索引(即,它出现在代码序列中的位置)。由于此示例是使用3次迭代生成的:-iterations=3迭代索引的范围从0到2。

排除第一列和最后一列,其余列处于循环中。循环从0开始按顺序编号。

从上面的示例输出中,我们知道以下内容:

  • 指令[1,0]已在第1周发送
  • 指令[1,0]在第2周期开始执行
  • 指令[1,0]在第4周期到达写回阶段
  • 指令[1,0]在第10周期退休

指令[1,0](即,来自迭代#1的vmulps)不必在调度程序的队列中等待操作数变为可用。在调度vmulps时,操作数已经可用,并且管道JFPU1已准备好提供另一条指令。因此可以在JFPU1管道上立即发出指令。事实证明,该指令仅在调度程序的队列中花费了1cy。

在回写阶段和退出事件之间存在5个周期的间隔。这是因为指令必须按程序顺序退出,因此[1,0]必须等待[0,2]首先退出(即,它必须等到第10周期)。

在该示例中,所有指令都在RAW(Read After Write)依赖关系链中。由vmulps写入的寄存器%xmm2立即由第一个vhaddps使用,第一个vhaddps写入的寄存器%xmm3由第二个vhaddps使用。长数据依赖性对ILP(指令级并行)产生负面影响。

在点积示例中,来自不同迭代的指令引入了反依赖性。但是,可以在寄存器重命名阶段删除这些依赖项(以分配寄存器别名为代价,因此消耗物理寄存器)。

表平均等待时间有助于诊断由于存在长延迟指令和可能限制ILP的长数据依赖性而导致的性能问题。请注意,默认情况下,llvm-mca假定dispatch事件和issue事件之间至少有1cy。

当性能受到数据依赖性和/或长等待时间指令的限制时,与在调度程序队列中花费的总循环数相比,预期在就绪状态下所花费的周期数非常少。两个计数器之间的差异很好地指示了影响数据依赖性对指令执行的影响程度。当性能主要受缺乏硬件资源的限制时,两个计数器之间的增量很小。然而,在队列中花费的周期数趋于更大(即,大于1-3cy),尤其是与其他低延迟指令相比时。