提问人:George2 提问时间:3/28/2009 最后编辑:Peter CordesGeorge2 更新时间:9/24/2020 访问量:77251
每个汇编指令需要多少个 CPU 周期?
How many CPU cycles are needed for each assembly instruction?
问:
我听说网上有一本英特尔的书,它描述了特定汇编指令所需的 CPU 周期,但我找不到它(经过努力)。谁能告诉我如何找到CPU周期?
下面是一个示例,在下面的代码中,mov/lock 是 1 个 CPU 周期,xchg 是 3 个 CPU 周期。
// This part is Platform dependent!
#ifdef WIN32
inline int CPP_SpinLock::TestAndSet(int* pTargetAddress,
int nValue)
{
__asm
{
mov edx, dword ptr [pTargetAddress]
mov eax, nValue
lock xchg eax, dword ptr [edx]
}
// mov = 1 CPU cycle
// lock = 1 CPU cycle
// xchg = 3 CPU cycles
}
#endif // WIN32
顺便说一句:这是我发布的代码的 URL:http://www.codeproject.com/KB/threads/spinlocks.aspx
答:
给定流水线、无序处理、微码、多核处理器等,不能保证汇编代码的特定部分将恰好花费 x 个 CPU 周期/时钟周期/任何周期。
如果存在这样的参考,它只能在给定特定架构的情况下提供广泛的概括,并且根据微码的实现方式,您可能会发现 Pentium M 与 Core 2 Duo 不同,后者不同于 AMD 双核等。
请注意,本文是在 2000 年更新的,并且是更早写的。即使是奔腾 4 也很难确定指令时序 - PIII、PII 和原始奔腾更容易,引用的文本可能基于那些具有更明确定义的指令时序的早期处理器。
如今,人们通常使用统计分析来估计代码时序。
评论
在 x86 上测量和计算 CPU 周期不再有意义。
首先,问问自己,你正在计算哪个 CPU 的周期数?核心-2?速龙?奔腾-M?原子?所有这些 CPU 都执行 x86 代码,但它们都有不同的执行时间。执行甚至在同一 CPU 的不同步进之间有所不同。
最后一个有意义的循环计数的 x86 是 Pentium-Pro。
还要考虑的是,在 CPU 内部,大多数指令被转码为微码,并由内部执行单元无序执行,该执行单元甚至看起来都不像 x86。单个 CPU 指令的性能取决于内部执行单元中有多少资源可用。
因此,指令的时间不仅取决于指令本身,还取决于周围的代码。
无论如何:您可以估计不同处理器的吞吐量资源使用情况和指令延迟。相关信息可在 Intel 和 AMD 网站上找到。
Agner Fog在他的网站上有一个非常好的总结。请参阅指令表,了解延迟、吞吐量和 uop 计数。请参阅微观建筑 PDF 以了解如何解释这些内容。
但请注意,-with-memory 不具有可预测的性能,即使您只查看一个 CPU 型号。即使在 L1D 缓存中缓存行已经很热的无争用情况下,作为一个完整的内存屏障也意味着它的影响很大程度上取决于加载和存储到周围代码中的其他地址。xchg
顺便说一句 - 因为您的示例代码是一个无锁数据结构基本构建块:您是否考虑过使用编译器内置函数?在 win32 上,您可以包含 intrin.h 并使用 _InterlockedExchange 等函数。
这将为您提供更好的执行时间,因为编译器可以内联指令。内联汇编程序始终强制编译器禁用围绕 asm 代码的优化。
评论
其他答案说,不可能准确预测在现代CPU上运行的代码的性能是正确的,但这并不意味着延迟是未知的,或者知道它们是无用的。
英特尔和AMD处理器的确切延迟列在Agner Fog的指令表中。另请参阅 Intel 64 和 IA-32 架构优化参考手册,以及 AMD 和 Intel® x86 处理器的指令延迟和吞吐量(摘自 Can Berk Güder 现已删除的仅链接答案)。AMD在自己的网站上也有pdf手册,其中包含其官方价值观。
对于(微)优化紧密循环,了解每条指令的延迟对于手动尝试调度代码有很大帮助。程序员可以进行很多编译器无法做到的优化(因为编译器不能保证它不会改变程序的含义)。
当然,这仍然需要你了解很多关于CPU的其他细节,比如它的流水线有多深,每个周期可以发出多少条指令,执行单元的数量等等。当然,这些数字因不同的 CPU 而异。但是您通常可以得出一个合理的平均值,该平均值或多或少适用于所有 CPU。
但值得注意的是,在这个级别上优化几行代码也是很多工作。而且很容易做出一些被证明是悲观的东西。现代 CPU 非常复杂,它们非常努力地从糟糕的代码中获得良好的性能。但也有一些情况,他们无法有效地处理,或者你认为你很聪明,可以制作高效的代码,结果会减慢 CPU 的速度。
编辑查看英特尔的优化手册,表 C-13: 第一列是指令类型,然后是每个 CPUID 的延迟列数。CPUID 指示这些数字适用于哪个处理器家族,并在文档的其他位置进行了说明。延迟指定在指令结果可用之前需要多少个周期,因此这是您要查找的数字。
吞吐量列显示每个周期可以执行多少条此类指令。
在此表中查找 xchg,我们看到根据 CPU 系列的不同,它需要 1-3 个周期,而 mov 需要 0.5-1 个周期。这些是针对指令的寄存器到寄存器形式的,而不是用于有内存的,后者要慢得多。更重要的是,延迟和对周围代码的影响变化很大(当与另一个内核争用时,速度要慢得多),因此只看最好的情况是错误的。(我还没有查过每个CPUID的含义,但我认为.5是针对Pentium 4的,它以两倍的速度运行芯片的某些组件,允许它在半个周期内完成工作)lock xchg
但是,我真的不明白您打算将此信息用于什么目的,但是如果您知道运行代码的确切 CPU 系列,那么将延迟相加会告诉您执行此指令序列所需的最小周期数。
评论
mov
锁定 XCHG eax、DWORD PTR [edx]
请注意,锁将锁定所有内核的内存提取内存,这在某些多核上可能需要 100 个周期,并且还需要刷新缓存行。它还会使管道停止。所以我不会担心其余的。
因此,最佳性能又回到了调整算法的关键区域。
请注意,在单核上,您可以通过移除锁来优化这一点,但对于多核,这是必需的。
现代 CPU 是复杂的野兽,使用流水线、超标量执行和无序执行等技术,这使得性能分析变得困难......但并非不可能!
虽然您不能再简单地将指令流的延迟相加以获得总运行时,但您仍然可以(通常)对某些代码段(尤其是循环)的行为进行高度准确的分析,如下所述和其他链接资源中所述。
指令时间
首先,您需要实际的时间安排。这些因 CPU 架构而异,但目前 x86 时序的最佳资源是 Agner Fog 的指令表。这些表格涵盖了不少于 30 种不同的微架构,列出了指令延迟,即指令从输入准备到输出所需的最小/典型时间。用 Agner 的话来说:
延迟:这是指令在 依赖链。这些数字是最小值。缓存未命中, 错位和异常可能会增加时钟计数 大大。在启用超线程的情况下,使用超线程 另一个线程中的执行单元会导致性能下降。 非正态数、NAN 和无穷大不会增加延迟。这 使用的时间单位是内核时钟周期,而不是参考时钟周期 由时间戳计数器给出。
因此,例如,该指令的延迟为一个周期,因此一系列依赖的添加指令(如图所示)每个周期的延迟为 1 个周期:add
add
add eax, eax
add eax, eax
add eax, eax
add eax, eax # total latency of 4 cycles for these 4 adds
请注意,这并不意味着每个指令只需要 1 个周期。例如,如果添加指令不依赖于,则在现代芯片上,所有 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 提供了一个指标来捕获一些潜在的并行性,称为倒数吞吐量:
倒数吞吐量:一系列同类独立指令的每条指令的平均内核时钟周期数 在同一线程中。
为此,这意味着每个周期最多可以执行 4 条指令(给出 的倒数吞吐量 )。add
0.25
add
1 / 4 = 0.25
倒数吞吐量数字还暗示了指令的流水线能力。例如,在最新的 x86 芯片上,指令的常见形式具有 3 个周期的延迟,并且内部只有一个执行单元可以处理它们(与通常具有四个可添加单元的执行单元不同)。然而,对于一长串独立指令,观察到的吞吐量是 1/周期,而不是每 3 个周期 1 个,因为延迟为 3。原因是该单元是流水线的:它可以在每个周期开始一个新的循环,即使之前的乘法尚未完成。imul
add
imul
imul
imul
这意味着一系列独立指令每个周期最多可以以 1 个的速度运行,但一系列相关指令每 3 个周期只能以 1 个周期运行(因为在前一个指令的结果准备好之前,下一个指令无法启动)。imul
imul
imul
因此,有了这些信息,您就可以开始了解如何分析现代 CPU 上的指令时序。
详细分析
尽管如此,以上只是触及表面。现在,您可以通过多种方式查看一系列指令(延迟或吞吐量),并且可能不清楚使用哪种指令。
此外,上述数字还未捕获其他限制,例如某些指令在 CPU 中争用相同的资源,以及 CPU 管道其他部分的限制(例如指令解码),这可能会导致整体吞吐量低于仅通过查看延迟和吞吐量计算的吞吐量。除此之外,你还有“超越 ALU”的因素,例如内存访问和分支预测:整个主题本身 - 你基本上可以很好地建模这些主题,但这需要工作。例如,这是最近的一篇文章,其中的答案详细涵盖了大多数相关因素。
涵盖所有细节会使这个已经很长的答案的大小增加 10 倍或更多,所以我只会向您指出最好的资源。Agner Fog 有一个优化组件指南,详细介绍了对循环的精确分析,其中包含十几个指令。请参阅当前版本的 PDF 中第 95 页开始的“12.7 向量循环瓶颈分析示例”。
基本思想是创建一个表,每条指令一行,并标记每条指令使用的执行资源。这使您可以查看任何吞吐量瓶颈。此外,您还需要检查循环中是否存在携带的依赖关系,以查看其中是否有任何依赖关系限制了吞吐量(有关复杂情况,请参阅“12.16 分析依赖关系”)。
如果您不想手动完成,英特尔已经发布了英特尔架构代码分析器,这是一个可以自动执行此分析的工具。它目前还没有在Skylake之外进行更新,但对于Kaby Lake来说,结果仍然很大程度上是合理的,因为微架构没有太大变化,因此时间仍然具有可比性。这个答案非常详细,并提供了示例输出,用户指南还不错(尽管它相对于最新版本已经过时了)。
其他来源
Agner 通常会在新架构发布后不久提供时序,但您也可以查看 instlatx64 中类似组织的时序和结果。结果涵盖了许多有趣的旧芯片,而新芯片通常很快就会出现。结果与Agner的结果基本一致,只有少数例外。您还可以在此页面上找到内存延迟和其他值。InstLatX86
InstLatX64
您甚至可以直接从英特尔的 IA32 和英特尔 64 优化手册的附录 C:指令延迟和吞吐量 (APPENDIX C: INSTRUCTION LATENCY AND THROUGHPUT) 中获取时序结果。就我个人而言,我更喜欢 Agner 的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且更易于使用,因为它们提供电子表格和 PDF 版本。
最后,x86 标签 wiki 提供了大量关于 x86 优化的资源,包括指向如何对代码序列进行周期精确分析的其他示例的链接。
如果您想更深入地了解上述“数据流分析”的类型,我建议您阅读数据流图旋风式介绍。
评论
lea
... (instregex "PSLLDri")>;
评论
xchg
lock
lock
lock
lock
cmpxchg