每个汇编指令需要多less个CPU周期?
我听说有英特尔在线书,它描述了特定汇编指令所需的CPU周期,但是我找不到(经过努力)。 任何人都可以告诉我如何findCPU周期吗?
下面是一个例子,在下面的代码中,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
给定stream水线,乱序处理,微码,多核处理器等,不能保证汇编代码的特定部分将精确地占用x个CPU周期/时钟周期/任何周期。
如果存在这样的参考,那么只能给出一个特定体系结构的广泛概括,根据微代码的实现情况,可能会发现Pentium M与Core 2 Duo不同,它不同于AMD双核等等
请注意,这篇文章是在2000年更新的,并且写得较早。 即使是奔腾4,在指令时机方面也很难确定 – PIII,PII和原来的Pentium比较容易,所引用的文本可能是基于那些早期处理器具有更明确的指令时序的。
现在人们通常使用统计分析来进行代码时序估计。
其他的答案是不可能准确地预测在现代CPU上运行的代码的性能是真实的,但这并不意味着延迟是未知的,或者知道它们是无用的。
Agner Fog的指令表中列出了Intel和AMD处理器的确切延迟时间。 另请参阅英特尔®64和IA-32架构优化参考手册以及AMD和Intel x86处理器的指令延迟和吞吐量 (来自Can BerkGüder现在删除的仅链接答案)。 AMD公司还在自己的网站上提供了官方价值的pdf手册。
对于(微)优化紧密循环,了解每条指令的延迟可以帮助人们手动地尝试安排代码。 程序员可以做很多优化,编译器不能(因为编译器不能保证它不会改变程序的含义)。
当然,这仍然需要你了解许多关于CPU的其他细节,例如stream水线的深度,每个周期可以发出多less指令,执行单元的数量等等。 当然,这些数字因不同的CPU而不同。 但是你可以经常想出一个合理的平均值,或多或less地适用于所有的CPU。
值得注意的是,在这个级别上甚至可以优化几行代码也是很多工作。 而且很容易使事情变成一个悲观的事情。 现代的CPU非常复杂,他们非常努力地从糟糕的代码中获得良好的性能。 但是也有些情况下,他们无法有效地处理,或者你认为自己聪明并且有效的代码,结果就是减慢了CPU速度。
编辑查看英特尔的优化手册,表C-13:第一列是指令types,然后每个CPUID有一些列延迟。 CPUID指示数字适用于哪个处理器系列,并在文档的其他地方进行说明。 延迟指定在指令结果可用之前需要多less个周期,因此这是您要查找的数字。
吞吐量列显示每个周期可以执行多less种types的指令。
在这张表中查找xchg,我们看到,取决于CPU系列,需要1-3个周期,而mov需要0.5-1。 这些用于指令的寄存器到寄存器forms,而不是lock xchg
带有内存的lock xchg
,这是慢得多的。 更重要的是,巨大的variables延迟和对周围代码的影响(当与另一个核心竞争时会慢得多),所以只看最好的情况是一个错误。 (我还没有查找每个CPUID的含义,但是我认为.5是针对Pentium 4的,它以双倍速度运行芯片的某些组件,允许它以半个周期完成)
但是,我不明白你打算如何使用这些信息,但是如果你知道代码正在运行的确切的CPU系列,那么累计延迟就会告诉你执行这个指令序列所需的最less周期数。
测量和计算CPU周期在x86上是没有意义的。
首先,问自己哪个CPU计数周期? 酷睿2? 一个Athlon? 奔腾-M? primefaces? 所有这些CPU都执行x86代码,但是它们都有不同的执行时间。 执行甚至在同一CPU的不同步进之间也会有所不同。
循环计数有意义的最后一个x86是Pentium-Pro。
另外要考虑的是,在CPU内部,大多数指令被转码为微码,并且由内部执行单元乱序执行,甚至不像x86那样。 单个CPU指令的性能取决于内部执行单元中有多less资源可用。
因此,指令的时间不仅取决于指令本身,还取决于周围的代码。
无论如何:您可以估计不同处理器的吞吐量 – 资源使用情况和指令延迟。 相关信息可以在英特尔和AMD网站上find。
Agner Fog在他的网站上有一个很好的总结。 有关延迟,吞吐量和uop计数,请参阅指令表。 看微架构PDF学习如何解释这些。
但是请注意,即使只查看一个CPU型号, xchg
-with-memory也不具有可预测的性能。 即使在L1Dcaching中的caching线已经很热的无争用情况下,作为一个完整的内存屏障将意味着它的影响很大程度上取决于加载和存储到周围代码中的其他地址。
顺便说一句 – 因为你的示例代码是一个无锁数据结构的基本构build块:你有没有考虑过使用编译器内置函数? 在win32上,可以包含intrin.h并使用_InterlockedExchange之类的函数。
这会给你更好的执行时间,因为编译器可以内联指令。 内联汇编程序总是强制编译器禁用围绕asm代码的优化。
现代的CPU是复杂的动物,使用stream水线 , 超标量执行和无序执行等技术,使性能分析变得困难… 但并非不可能 !
虽然您不能再简单地将指令stream的延迟加在一起以获得总体运行时间,但您仍然可以(通常)高度准确地分析某段代码(特别是循环)的行为,如下所述其他链接的资源。
指令计时
首先,你需要实际的时间。 这些因CPU架构而异,最好的资源是Agner Fog的指令表 。 覆盖不less于30种不同的微架构,这些表格列出了指令延迟 ,这是指令从input准备到输出可用的最小/典型时间。 用阿格的话来说:
延迟:这是指令在依赖链中产生的延迟。 数字是最小值。 高速caching未命中,未alignment和exception可能会显着增加时钟计数。 在启用超线程的情况下,在其他线程中使用相同的执行单元会导致性能下降。 非正规数,NAN和无穷大不会增加延迟。 使用的时间单位是核心时钟周期,而不是时间戳计数器给出的参考时钟周期。
因此,例如, add
指令的延迟时间为一个周期,所以如图所示,一系列相关的 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提供了一个度量,它捕获了一些这种潜在的并行性,称为相互吞吐量 :
互惠吞吐量:同一线程中一系列相同types的独立指令的每条指令的平均核心时钟周期数。
对于add
这被列为0.25
这意味着每个周期最多可以执行4条add
指令(给出1 / 4 = 0.25
的相反吞吐量)。
互逆吞吐量数字也提示了指令的stream水线能力。 例如,在最新的x86芯片上, imul
指令的常见forms具有3个周期的延迟,并且在内部只有一个执行单元可以处理它们(不同于通常具有四个可添加单元的add)。 然而,对于一系列长的独立指令,观察到的吞吐量是1 /周期,而不是每3个周期就有1个周期,因为你可能期望给出3的等待时间。原因在于imul
单元是stream水线的:它可以在每个周期 开始一个新的,即使以前的乘法还没有完成。
这意味着一系列独立的指令每个周期最多可以运行1次,但是一系列依赖的指令只会在每3个周期运行一次(因为下一个imul
不能启动,直到前一个指令的结果准备就绪)。
因此,通过这些信息,您可以开始了解如何分析现代CPU上的指令时序。
详细的分析
尽pipe如此,以上只是表面上的划痕。 您现在有多种查看一系列指令(延迟或吞吐量)的方法,并且可能不清楚要使用哪一种。
此外,上述数字还没有捕捉到其他的限制,例如某些指令在CPU内竞争相同的资源,以及在CPUstream水线的其他部分(如指令解码)中的限制,这可能导致较低的整体吞吐量比您仅仅通过查看延迟和吞吐量来计算得出。 除此之外,你还有“超越ALU”的因素,比如内存访问和分支预测:整个主题都是自己的 – 你可以对这些主题进行很好的build模,但这需要工作。 例如,这里有一个最近的post ,其中的答案涵盖了大部分相关因素。
涵盖所有的细节将把这个已经很长的答案的规模增加10倍或更多,所以我只是指出你最好的资源。 Agner Fog有一个优化组装 指南 ,详细介绍了十几条指令的循环精确分析。 请参阅“ 12.7在当前版本的PDF中从第95页开始的向量循环中的瓶颈分析示例”。
其基本思想是创build一个表,每条指令一行,并标记每个使用的执行资源。 这让您看到任何吞吐量瓶颈。 此外,您需要检查循环中是否存在依赖性,以查看是否有任何限制吞吐量(请参阅“ 12.16分析依赖性”以了解复杂情况)。
如果您不想亲手操作,英特尔已经发布了英特尔架构代码分析器 ,这是一个自动化分析的工具。 目前还没有Skylake以上的更新,但由于微架构没有太大变化,因此Kaby Lake的结果仍然基本合理,因此时间保持可比性。 这个答案进入了很多的细节,并提供了示例输出, 用户指南并不是一半坏(虽然它已经过时了,相对于最新版本)。
其他来源
Agner通常会在新架构发布之后为其提供时序,但是您也可以在InstLatX86
和InstLatX64
结果中查看instlatx64以获得类似的组织时序。 结果涵盖了很多有趣的旧芯片,新芯片通常显示相当快。 结果大部分与Agner一致,除了less数例外。 您也可以在此页面上find内存延迟和其他值。
您甚至可以在附录C:INSTRUCTION LATENCY AND THROUGHPUT的IA32和Intel 64优化手册中直接获得英特尔的计时结果。 就个人而言,我更喜欢Agner的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且更容易使用,因为它们提供电子表格和PDF版本。
最后, x86标签wiki在x86优化方面拥有丰富的资源,包括指向如何对代码序列进行循环精确分析的其他示例的链接。
lockingxchg eax,dword ptr [edx]
注意,锁会locking所有内核的内存获取内存,在一些多核上这可能需要100个周期,并且还需要刷新caching行。 它也将拖延pipe道。 所以我不会担心其余的。
所以最佳性能可以回到调整algorithm关键区域。
请注意,在单个内核上,您可以通过删除locking来优化此操作,但是对于多内核而言,则需要此操作。