现代 CPU 是复杂的野兽,使用流水线、超标量执行和乱序执行以及其他使性能分析变得困难的技术……但并非不可能!
虽然您不能再简单地将指令流的延迟相加以获得总运行时间,但您仍然可以(通常)高度准确地分析某些代码(尤其是循环)的行为,如下所述和其他链接资源。
指令时间
首先,您需要实际的时间。这些因 CPU 架构而异,但目前 x86 时序的最佳资源是 Agner Fog 的指令表。这些表涵盖不少于三十种不同的微架构,列出了指令延迟,这是一条指令从准备好输入到可用输出的最小/典型时间。用阿格纳的话来说:
延迟: 这是指令在依赖链中生成的延迟。数字是最小值。高速缓存未命中、未对齐和异常可能会显着增加时钟计数。在启用超线程的情况下,在另一个线程中使用相同的执行单元会导致性能下降。非正规数、NAN 和无穷大不会增加延迟。使用的时间单位是核心时钟周期,而不是时间戳计数器给出的参考时钟周期。
因此,例如,该add
指令具有一个周期的延迟,因此如图所示,一系列相关的加法指令将具有每个周期 1 个周期的延迟add
:
add eax, eax
add eax, eax
add eax, eax
add eax, eax # total latency of 4 cycles for these 4 adds
请注意,这并不意味着add
每条指令只需 1 个周期。例如,如果 add 指令不依赖,那么在现代芯片上,所有 4 个 add 指令都可以在同一个周期内独立执行:
add eax, eax
add ebx, ebx
add ecx, ecx
add edx, edx # these 4 instructions might all execute, in parallel in a single cycle
Agner 提供了一个衡量这种潜在并行性的指标,称为互惠吞吐量:
倒数吞吐量: 同一线程中一系列同类独立指令的每条指令的平均核心时钟周期数。
因为add
这被列为0.25
意味着add
每个周期最多可以执行 4 条指令(给出倒数的吞吐量1 / 4 = 0.25
)。
倒数的吞吐量数也暗示了指令的流水线能力。例如,在最新的 x86 芯片上,imul
指令的常见形式具有 3 个周期的延迟,并且在内部只有一个执行单元可以处理它们(不像add
通常有四个可添加的单元)。然而,对于一长串独立imul
指令,观察到的吞吐量是 1 个/周期,而不是您可能期望的每 3 个周期 1 个,因为延迟为 3。原因是该imul
单元是流水线的:它可以在每个周期开始一个新的,即使在之前的乘法还没有完成。imul
这意味着一系列独立 imul
指令每个周期最多可以运行 1 个,但一系列相关 imul
指令每 3 个周期只能运行 1 个(因为下一个指令imul
在前一个指令的结果准备好之前无法开始)。
因此,有了这些信息,您就可以开始了解如何分析现代 CPU 上的指令时序。
详细分析
尽管如此,以上只是表面上的。您现在可以通过多种方式查看一系列指令(延迟或吞吐量),并且可能不清楚使用哪种方式。
此外,还有其他一些限制,例如某些指令在 CPU 内竞争相同的资源,以及 CPU 流水线其他部分的限制(例如指令解码)可能导致较低的总吞吐量比您仅通过查看延迟和吞吐量来计算的。除此之外,您还有“ALU 之外”的因素,例如内存访问和分支预测:整个主题本身——您可以很好地对这些进行建模,但这需要工作。例如,这是最近的一篇文章,其中答案详细介绍了大多数相关因素。
涵盖所有细节会使这个已经很长的答案的大小增加 10 倍或更多,所以我只会为您指出最佳资源。Agner Fog 有一个Optimizing Asembly 指南,其中详细介绍了包含十几个指令的循环的精确分析。请参阅 PDF 当前版本的第 95 页开始的“ 12.7矢量循环中的瓶颈分析示例”。
基本思想是创建一个表,每条指令有一行,并标记每条指令使用的执行资源。这让您可以看到任何吞吐量瓶颈。此外,您需要检查循环中携带的依赖项,以查看是否有任何限制吞吐量(有关复杂情况,请参阅“ 12.16分析依赖项”)。
如果您不想手动进行,英特尔发布了英特尔架构代码分析器,这是一个自动执行此分析的工具。它目前尚未在 Skylake 之外进行更新,但对于 Kaby Lake 而言,结果在很大程度上仍然是合理的,因为微架构没有太大变化,因此时间保持可比性。这个答案涉及很多细节并提供了示例输出,用户指南也不错(尽管它相对于最新版本已经过时)。
其他来源
Agner 通常会在新架构发布后不久为其提供时序,但您也可以查看instlatx64InstLatX86
以了解在和InstLatX64
结果中类似组织的时序。结果涵盖了很多有趣的旧筹码,而新筹码通常会很快出现。结果与 Agner 的结果基本一致,除了一些例外。您还可以在此页面上找到内存延迟和其他值。
您甚至可以在附录 C:指令延迟和吞吐量的IA32 和 Intel 64 优化手册中直接从 Intel 获得时序结果。我个人更喜欢 Agner 的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且更易于使用,因为它们提供了电子表格和 PDF 版本。
最后,x86 标签 wiki有大量关于 x86 优化的资源,包括指向如何对代码序列进行循环精确分析的其他示例的链接。
如果您想更深入地了解上述“数据流分析”的类型,我会推荐A Whirlwind Introduction to Data Flow Graphs。