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

标签 performance assembly x86 cpu-architecture cpu-cycles

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

最佳答案

现代 CPU 是复杂的野兽,使用 pipelining , superscalar execution , 和 out-of-order execution在其他使性能分析变得困难的技术中... 但并非不可能 !

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

教学时间

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

Latency: This is the delay that the instruction generates in a dependency chain. The numbers are minimum values. Cache misses, misalignment, and exceptions may increase the clock counts considerably. Where hyperthreading is enabled, the use of the same execution units in the other thread leads to inferior performance. Denormal numbers, NAN's and infinity do not increase the latency. The time unit used is core clock cycles, not the reference clock cycles given by the time stamp counter.



例如,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 提供了一个度量来捕获一些这种潜在的并行性,称为互惠吞吐量:

Reciprocal throughput: The average number of core clock cycles per instruction for a series of independent instructions of the same kind in the same thread.



对于 add这被列为 0.25意味着最多 4 add指令可以在每个周期执行(给出 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”的因素,例如内存访问和分支预测:整个主题本身 - 您大多可以很好地对这些进行建模,但这需要工作。例如这里是 recent post答案在一定程度上涵盖了大多数相关因素。

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

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

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

其他来源

Agner 通常会在新架构发布后不久为其提供时间安排,但您也可以查看 instlatx64对于 InstLatX86 中类似组织的计时和 InstLatX64结果。结果涵盖了很多有趣的旧芯片,新芯片通常很快就会出现。结果与 Agner 的结果基本一致,但这里和那里也有一些异常(exception)。您还可以在此页面上找到内存延迟和其他值。

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

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

如果您想更深入地了解上述“数据流分析”的类型,我会推荐 A Whirlwind Introduction to Data Flow Graphs .

关于performance - 每条汇编指令需要多少个 CPU 周期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/692718/

相关文章:

assembly - 关于 bsr 和 lzcnt 的困惑

程序集 8086 - 异或操作

assembly - X86 切换到 32 位保护模式

mysql - 多个 MAX() 函数,每个函数具有不同的条件

c - 使用clock()来统计程序执行时间

c++ - 虚拟析构函数性能

assembly - NASM 移位运算符

mysql - 在 SQL 表中存储记录数

assembly - clflush 会刷新 L1i 吗?

linux - 为什么 RCX 不用于将参数传递给系统调用,而是用 R10 代替?