【问题标题】:How many CPU cycles are needed for each assembly instruction?每条汇编指令需要多少个 CPU 周期?
【发布时间】:2010-10-16 02:47:50
【问题描述】:

我听说有 Intel 在线书籍描述了特定汇编指令所需的 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

【问题讨论】:

  • 你觉得这个stackoverflow.com/questions/138932/…>有帮助吗?
  • 锁前缀在xchg上不是多余的吗?我在想这是一个隐含锁定的指令?还是多处理器使用需要它?当涉及到多处理器配置时,我似乎记得隐含锁和显式锁之间的一些区别。
  • 在超级用户上:superuser.com/questions/643442/…
  • @BrianKnoblauch:是的,有记忆的xchg 有一个隐含的lock 前缀。所有其他指令都需要一个 lock 前缀,以便相对于其他 CPU 的观察而言是原子的,但非 locked 版本 can be useful on uniprocessor systems,这可能就是为什么 lock 对于像 cmpxchg 这样的东西不是隐含的.
  • @George2 beeonrope 添加了一个新答案,我认为它最接近回答您的问题 - 如果您有相同的感觉,请考虑查看并选择它。

标签: performance assembly x86 cpu-architecture cpu-cycles


【解决方案1】:

现代 CPU 是复杂的野兽,使用 pipeliningsuperscalar executionout-of-order execution 以及其他使性能分析变得困难的技术...但并非不可能

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

指令时序

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

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

因此,例如,add 指令的延迟为一个周期,因此一系列相关添加指令,如图所示,每个 add 的延迟为 1 个周期:

add eax, eax
add eax, eax
add eax, eax
add eax, eax  # total latency of 4 cycles for these 4 adds

请注意,这并不意味着add 指令每个只需要 1 个周期。例如,如果添加指令依赖,那么在现代芯片上,所有 4 条添加指令都可以在同一周期内独立执行:

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,这意味着每个周期最多可以执行4条add指令(提供1 / 4 = 0.25的倒数吞吐量)。

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

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

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

详细分析

不过,以上内容只是表面上的。您现在可以通过多种方式查看一系列指令(延迟或吞吐量),可能不清楚使用哪种方式。

此外,上述数字还没有涵盖其他限制,例如某些指令在 CPU 内竞争相同资源的事实,以及可能导致 CPU 流水线其他部分的限制(例如指令解码)总吞吐量比您仅通过查看延迟和吞吐量计算的要低。除此之外,您还有“ALU 之外”的因素,例如内存访问和分支预测:整个主题本身——您可以很好地对这些进行建模,但这需要工作。例如这里是recent post,其中的答案详细涵盖了大多数相关因素。

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

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

如果您不想手动进行,英特尔发布了Intel Architecture Code Analyzer,这是一个自动执行此分析的工具。它目前尚未在 Skylake 之外进行更新,但对于 Kaby Lake 而言,结果在很大程度上仍然是合理的,因为微架构没有太大变化,因此时间保持可比性。 This answer 介绍了很多细节并提供了示例输出,user's guide 也不错(尽管相对于最新版本来说它已经过时了)。

其他来源

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

您甚至可以在 附录 C:指令延迟和吞吐量中的 IA32 and Intel 64 optimization manual 中直接从 Intel 获得计时结果。我个人更喜欢 Agner 的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且更易于使用,因为它们提供了电子表格和 PDF 版本。

最后,x86 tag wiki 拥有丰富的 x86 优化资源,包括指向如何对代码序列进行循环精确分析的其他示例的链接。

如果您想深入了解上述“数据流分析”的类型,我建议您使用A Whirlwind Introduction to Data Flow Graphs

【讨论】:

  • 不是 0.33,如果一个 imul 完成每个周期。我想你还没有完成编辑。非流水线将是 3c inverse 吞吐量。但是,如果每个周期都完成,那将使吞吐量为 1。
  • @PeterCordes 旨在涵盖“某些指令在 CPU 内竞争相同的执行单元”,它使用“执行单元”来广泛涵盖调度的所有容量/专业限制,例如端口、ALU/EU(这两个在最近的拱门上大部分可以互换)、特定于指令的限制(例如lea)。正如我在那之后立即指出的那样,解释如何在考虑所有因素的情况下进行完整的端到端分析将非常耗时,而且大部分只是重复已经准备好的其他材料,其中一些我链接到。跨度>
  • @PeterCordes LLVM 人员显然最近从英特尔那里得到了有关 Sandy Bridge uop 延迟及以上的详细信息,编码的知识将最终出现在 LLVM 的调度程序中。我们应该关注这个空间:reviews.llvm.org/rL307529 “另外请注意,此补丁之后将发布针对剩余目标架构 HSW、IVB、BDW、SKL 和 SKX 的其他补丁。”
  • 哦,是的,SnB 在 2 个端口上运行整数洗牌(没有 256b 版本)。嗯,稍后在同一个文件中,有很多新行,包括 port0 组中的... (instregex "PSLLDri")>;。所以我认为它毕竟是理智的。
  • @PeterCordes 和 BeeOnRope:看,the LLVM scheduler for Haswell was updated。它甚至给出了每条指令生成多少个微指令以及这些微指令可以发送到的端口集的细分。
【解决方案2】:

考虑到流水线、乱序处理、微码、多核处理器等,无法保证汇编代码的特定部分将占用 x 个 CPU 周期/时钟周期/任何周期。

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

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

现在人们通常使用统计分析来估计代码时序。

【讨论】:

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

其他答案所说的无法准确预测在现代 CPU 上运行的代码的性能是正确的,但这并不意味着延迟是未知的,或者知道它们是无用的。

Agner Fog's instruction tables 中列出了 Intel 和 AMD 处理器的确切延迟。另请参阅 Intel® 64 and IA-32 Architectures Optimization Reference ManualInstruction latencies and throughput for AMD and Intel x86 processors(来自 Can Berk Güder 现已删除的仅链接答案)。 AMD 在他们自己的网站上也有带有官方价值观的 pdf 手册。

对于(微)优化紧密循环,了解每条指令的延迟对于手动尝试安排代码有很大帮助。程序员可以做很多编译器做不到的优化(因为编译器不能保证不会改变程序的意思)。

当然,这仍然需要你了解 CPU 的很多其他细节,比如流水线的深度、每个周期可以发出多少条指令、执行单元的数量等等。当然,这些数字因不同的 CPU 而异。但您通常可以得出一个或多或少适用于所有 CPU 的合理平均值。

但值得注意的是,即使是在这个级别优化几行代码,也需要做很多工作。而且很容易做出一些结果是悲观的事情。现代 CPU 非常复杂,它们非常努力地从糟糕的代码中获得良好的性能。但也有一些情况他们无法有效处理,或者你认为你很聪明并且编写了高效的代码,结果却降低了 CPU 的速度。

编辑 查看英特尔的优化手册,表 C-13: 第一列是指令类型,然后是每个 CPUID 的延迟列数。 CPUID 指示编号适用于哪个处理器系列,并在文档的其他地方进行了说明。延迟指定在指令结果可用之前需要多少个周期,因此这是您要查找的数字。

吞吐量列显示每个周期可以执行多少此类指令。

在此表中查找 xchg,我们看到根据 CPU 系列,它需要 1-3 个周期,而 mov 需要 0.5-1。这些是针对寄存器到寄存器形式的指令,而不是针对带有内存的lock xchg,后者要慢得多。更重要的是,巨大的延迟和对周围代码的影响(当与另一个核心争用时会慢得多),所以只看最好的情况是错误的。 (我没有查看每个 CPUID 的含义,但我认为 0.5 是用于 Pentium 4 的,它以双倍速度运行芯片的某些组件,允许它在半个周期内完成工作)

不过,我并不真正了解您打算将这些信息用于什么目的,但如果您知道代码正在运行的确切 CPU 系列,那么将延迟加起来可以告诉您执行此操作所需的最小周期数指令序列。

【讨论】:

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

在 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

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


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

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

【讨论】:

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

lock xchg eax, dword ptr [edx]

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

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

注意在单核上,您可以通过移除锁来优化这一点,但多核需要它。

【讨论】:

    猜你喜欢
    • 2010-12-05
    • 2021-06-24
    • 2014-07-26
    • 2017-05-28
    • 1970-01-01
    • 2016-11-12
    • 2017-05-25
    • 1970-01-01
    • 2020-04-09
    相关资源
    最近更新 更多