每个汇编指令需要多少个 CPU 周期?

How many CPU cycles are needed for each assembly instruction?

提问人:George2 提问时间:3/28/2009 最后编辑:Peter CordesGeorge2 更新时间:9/24/2020 访问量:77251

问:

我听说网上有一本英特尔的书,它描述了特定汇编指令所需的 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

性能 组合 x86 CPU 架构 cpu 周期

评论

0赞 dirkgently 3/28/2009
你认为这<吗 stackoverflow.com/questions/138932/......>有什么帮助?
3赞 Brian Knoblauch 12/2/2010
锁前缀在 xchg 上不是多余的吗?我以为这是一个暗示锁定的指令?还是多处理器使用需要?我似乎记得在多处理器配置方面,隐式锁和显式锁之间存在一些差异。
1赞 Ciro Santilli OurBigBook.com 6/14/2015
在超级用户上:superuser.com/questions/643442/...
1赞 Peter Cordes 7/7/2017
@BrianKnoblauch:是的,with memory 有一个隐式前缀。所有其他指令都需要一个前缀才能成为原子指令,以便其他 CPU 进行观察,但非 ed 版本在单处理器系统上很有用,这可能就是为什么对于类似 的东西不隐式的原因。xchglocklocklocklockcmpxchg
2赞 Adam Davis 7/8/2017
@George2 beeonrope 添加了一个新的答案,我认为它最接近回答您的问题 - 如果您也有同样的感觉,请考虑查看它并选择它。

答:

30赞 Adam Davis 3/28/2009 #1

给定流水线、无序处理、微码、多核处理器等,不能保证汇编代码的特定部分将恰好花费 x 个 CPU 周期/时钟周期/任何周期。

如果存在这样的参考,它只能在给定特定架构的情况下提供广泛的概括,并且根据微码的实现方式,您可能会发现 Pentium M 与 Core 2 Duo 不同,后者不同于 AMD 双核等。

请注意,本文是在 2000 年更新的,并且是更早写的。即使是奔腾 4 也很难确定指令时序 - PIII、PII 和原始奔腾更容易,引用的文本可能基于那些具有更明确定义的指令时序的早期处理器。

如今,人们通常使用统计分析来估计代码时序。

评论

1赞 CDR 3/28/2009
优秀的答案!涵盖了人们可能遇到的每个反问题。
10赞 jalf 3/28/2009
从技术上讲并不完全准确。每条指令都有固定的持续时间/延迟,如Can Berk Güders回答中所述。由于您指出的原因,仅此一项只是故事的一部分。知道每条指令的延迟并不能告诉您它何时被安排。
6赞 Justicle 3/13/2012
@AdamDavis stackoverflow.com/a/692727/94239 按照要求简明扼要地回答了这个问题。英特尔指南确实按处理器型号细分了性能(如果您费心查看)。你的回答对 SO 的学习环境没有帮助,因为它本质上是说“甚至不要尝试”。
4赞 Adam Davis 3/13/2012
@Justicle我不同意。这个答案提供了人们可以查看以查找信息的手册,但它没有提供信息,或者更重要的是,它没有提供足够的信息来了解如何阅读手册和查找信息。我欢迎您阅读手册并提供这些指令将在 Core 系列中的处理器之一上采用的时钟周期数 - 您的选择 - 并忽略其余处理器。如果它像你说的那么简单,而我的答案是错误的,那么你应该能够轻松快速地做到这一点。通过提供确切的答案来证明我错了。
3赞 BeeOnRope 7/7/2017
这个答案太悲观了。你不能把周期数加在一起得到总延迟的总体想法是正确的,但这并不意味着你只是举手说现代CPU是一个黑匣子。在该模型中,您只需要使用一个稍微复杂的模型,其中指令是依赖关系图中的节点,这些节点具有延迟和与其他指令共享的一些吞吐量约束。Agners 指南详细介绍了它(他有每条指令的编号),英特尔的 IACA 在软件中实现了这个概念。其他注意事项适用。
15赞 Nils Pipenbrinck 3/28/2009 #2

在 x86 上测量和计算 CPU 周期不再有意义。

首先,问问自己,你正在计算哪个 CPU 的周期数?核心-2?速龙?奔腾-M?原子?所有这些 CPU 都执行 x86 代码,但它们都有不同的执行时间。执行甚至在同一 CPU 的不同步进之间有所不同。

最后一个有意义的循环计数的 x86 是 Pentium-Pro。

还要考虑的是,在 CPU 内部,大多数指令被转码为微码,并由内部执行单元无序执行,该执行单元甚至看起来都不像 x86。单个 CPU 指令的性能取决于内部执行单元中有多少资源可用。

因此,指令的时间不仅取决于指令本身,还取决于周围的代码。

无论如何:您可以估计不同处理器的吞吐量资源使用情况和指令延迟。相关信息可在 Intel 和 AMD 网站上找到。

Agner Fog在他的网站上有一个非常好的总结。请参阅指令表,了解延迟、吞吐量和 uop 计数。请参阅微观建筑 PDF 以了解如何解释这些内容。

http://www.agner.org/optimize

但请注意,-with-memory 不具有可预测的性能,即使您只查看一个 CPU 型号。即使在 L1D 缓存中缓存行已经很热的无争用情况下,作为一个完整的内存屏障也意味着它的影响很大程度上取决于加载和存储到周围代码中的其他地址。xchg


顺便说一句 - 因为您的示例代码是一个无锁数据结构基本构建块:您是否考虑过使用编译器内置函数?在 win32 上,您可以包含 intrin.h 并使用 _InterlockedExchange 等函数。

这将为您提供更好的执行时间,因为编译器可以内联指令。内联汇编程序始终强制编译器禁用围绕 asm 代码的优化。

评论

0赞 George2 3/29/2009
@Nils,我认为您的意思是指令的总运行时间,它取决于系统资源状态和调度。但我认为一旦指令执行,它将在特定架构的固定 CPU 周期内执行,对吗?
0赞 George2 3/29/2009
@Nils,代码示例只是为了我的学习自旋锁的学习目的,对于真正的编程实践,我一定会使用互锁函数。
0赞 George2 3/29/2009
顺便说一句:在 agner.org 上,汇编指令所需的CPU周期信息在哪里?我在这个网站上看了一段时间,但一无所获。你能给出 1-2 个链接吗?:-)
0赞 Justicle 3/11/2012
不回答问题。
4赞 BeeOnRope 7/7/2017
计算和加法指令时序是有效的,它只需要一个比过去更复杂的模型。事实上,对于许多没有外部因素(如 L1 未命中)的循环,这种计数可以为您提供循环准确的结果,或者几乎如此。
27赞 jalf 3/28/2009 #3

其他答案说,不可能准确预测在现代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 系列,那么将延迟相加会告诉您执行此指令序列所需的最小周期数。

评论

0赞 George2 3/29/2009
@jalf,您能指导我解释一下如何找到像 mov/xchg 这样的指令需要多少 CPU 周期吗?我查看了英特尔其他人推荐的文档,但对表格中每列的确切含义感到困惑。谢谢。
0赞 jalf 3/29/2009
latency 列显示从指令启动到结果可用所需的周期数。英特尔将其细分为不同的 CPUID,以显示不同 CPU 系列的值,xchg 根据 CPU 被列为 1-3 个周期,mov 为 0.5-1。
0赞 jalf 3/29/2009
编辑了我的帖子以添加这些详细信息
2赞 Peter Cordes 7/7/2017
最后一句话是假的:“然后将延迟相加,告诉你执行这一系列指令所需的最小周期数。不可以,因为两个负载可以并行运行。假设没有资源冲突(执行端口被其他指令窃取,延迟关键路径),则仅在单个 dep 链中执行延迟。mov
0赞 Ross Ridge 7/7/2017
@PeterCordes 在示例中,情况更糟,因为 XCHG 指令(带有冗余 LOCK 前缀)具有巨大的未知延迟,这使得任何基于图表的最小值都非常虚假。
8赞 ben 1/4/2010 #4

锁定 XCHG eax、DWORD PTR [edx]

请注意,锁将锁定所有内核的内存提取内存,这在某些多核上可能需要 100 个周期,并且还需要刷新缓存行。它还会使管道停止。所以我不会担心其余的。

因此,最佳性能又回到了调整算法的关键区域。

请注意,在单核上,您可以通过移除锁来优化这一点,但对于多核,这是必需的。

60赞 BeeOnRope 7/8/2017 #5

现代 CPU 是复杂的野兽,使用流水线超标量执行和无序执行等技术,这使得性能分析变得困难......但并非不可能

虽然您不能再简单地将指令流的延迟相加以获得总运行时,但您仍然可以(通常)对某些代码段(尤其是循环)的行为进行高度准确的分析,如下所述和其他链接资源中所述。

指令时间

首先,您需要实际的时间安排。这些因 CPU 架构而异,但目前 x86 时序的最佳资源是 Agner Fog 的指令表。这些表格涵盖了不少于 30 种不同的微架构,列出了指令延迟,即指令从输入准备到输出所需的最小/典型时间。用 Agner 的话来说:

延迟:这是指令在 依赖链。这些数字是最小值。缓存未命中, 错位和异常可能会增加时钟计数 大大。在启用超线程的情况下,使用超线程 另一个线程中的执行单元会导致性能下降。 非正态数、NAN 和无穷大不会增加延迟。这 使用的时间单位是内核时钟周期,而不是参考时钟周期 由时间戳计数器给出。

因此,例如,该指令的延迟为一个周期,因此一系列依赖的添加指令(如图所示)每个周期的延迟为 1 个周期:addadd

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 条指令(给出 的倒数吞吐量 )。add0.25add1 / 4 = 0.25

倒数吞吐量数字还暗示了指令的流水线能力。例如,在最新的 x86 芯片上,指令的常见形式具有 3 个周期的延迟,并且内部只有一个执行单元可以处理它们(与通常具有四个可添加单元的执行单元不同)。然而,对于一长串独立指令,观察到的吞吐量是 1/周期,而不是每 3 个周期 1 个,因为延迟为 3。原因是该单元是流水线的:它可以在每个周期开始一个新的循环,即使之前的乘法尚未完成。imuladdimulimulimul

这意味着一系列独立指令每个周期最多可以以 1 个的速度运行,但一系列相关指令每 3 个周期只能以 1 个周期运行(因为在前一个指令的结果准备好之前,下一个指令无法启动)。imulimulimul

因此,有了这些信息,您就可以开始了解如何分析现代 CPU 上的指令时序。

详细分析

尽管如此,以上只是触及表面。现在,您可以通过多种方式查看一系列指令(延迟或吞吐量),并且可能不清楚使用哪种指令。

此外,上述数字还未捕获其他限制,例如某些指令在 CPU 中争用相同的资源,以及 CPU 管道其他部分的限制(例如指令解码),这可能会导致整体吞吐量低于仅通过查看延迟和吞吐量计算的吞吐量。除此之外,你还有“超越 ALU”的因素,例如内存访问和分支预测:整个主题本身 - 你基本上可以很好地建模这些主题,但这需要工作。例如,这是最近的一篇文章,其中的答案详细涵盖了大多数相关因素。

涵盖所有细节会使这个已经很长的答案的大小增加 10 倍或更多,所以我只会向您指出最好的资源。Agner Fog 有一个优化组件指南,详细介绍了对循环的精确分析,其中包含十几个指令。请参阅当前版本的 PDF 中第 95 页开始的“12.7 向量循环瓶颈分析示例”。

基本思想是创建一个表,每条指令一行,并标记每条指令使用的执行资源。这使您可以查看任何吞吐量瓶颈。此外,您还需要检查循环中是否存在携带的依赖关系,以查看其中是否有任何依赖关系限制了吞吐量(有关复杂情况,请参阅“12.16 分析依赖关系”)。

如果您不想手动完成,英特尔已经发布了英特尔架构代码分析器,这是一个可以自动执行此分析的工具。它目前还没有在Skylake之外进行更新,但对于Kaby Lake来说,结果仍然很大程度上是合理的,因为微架构没有太大变化,因此时间仍然具有可比性。这个答案非常详细,并提供了示例输出,用户指南还不错(尽管它相对于最新版本已经过时了)。

其他来源

Agner 通常会在新架构发布后不久提供时序,但您也可以查看 instlatx64 中类似组织的时序和结果。结果涵盖了许多有趣的旧芯片,而新芯片通常很快就会出现。结果与Agner的结果基本一致,只有少数例外。您还可以在此页面上找到内存延迟和其他值。InstLatX86InstLatX64

您甚至可以直接从英特尔的 IA32 和英特尔 64 优化手册附录 C:指令延迟和吞吐量 (APPENDIX C: INSTRUCTION LATENCY AND THROUGHPUT) 中获取时序结果。就我个人而言,我更喜欢 Agner 的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且更易于使用,因为它们提供电子表格和 PDF 版本。

最后,x86 标签 wiki 提供了大量关于 x86 优化的资源,包括指向如何对代码序列进行周期精确分析的其他示例的链接。

如果您想更深入地了解上述“数据流分析”的类型,我建议您阅读数据流图旋风式介绍

评论

1赞 BeeOnRope 7/16/2017
@PeterCordes 它旨在通过“某些指令竞争 CPU 中的相同执行单元”来涵盖,它使用“执行单元”来广泛涵盖调度的所有容量/专业化限制,例如端口、ALU/EU(这两者在最近的 arch 上大多可以互换)、特定于指令的限制(例如)。正如我紧接着指出的那样,解释如何进行完整的端到端分析,考虑所有因素将非常长,而且大多数情况下只是重复已经准备好的其他材料,其中一些我链接到其中。lea
1赞 Iwillnotexist Idonotexist 7/18/2017
@PeterCordes LLVM的家伙们最近显然从英特尔那里得到了关于Sandy Bridge uop延迟的私密细节,编码后的知识最终将出现在LLVM的调度器中。我们应该关注这个空间: reviews.llvm.org/rL307529 “另请注意,此补丁之后将针对其余目标架构 HSW、IVB、BDW、SKL 和 SKX 提供额外的补丁。”
1赞 Peter Cordes 7/18/2017
哦,是的,SnB 在 2 个端口上运行整数随机播放(没有 256b 版本)。嗯,在同一个文件的后面,有很多新行,包括在 port0 组中。所以我认为这毕竟是理智的。... (instregex "PSLLDri")>;
1赞 Iwillnotexist Idonotexist 8/28/2017
@PeterCordes 和 BeeOnRope:看哪,Haswell 的 LLVM 调度器已更新。它甚至给出了每条指令生成多少个 uops 以及这些 uops 可以发送到的端口集的细分。
1赞 maxschlepzig 3/9/2020
关于其他来源:还有 uops.info/table.html - 这个交互式页面当然有一个很好的用户界面