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