我听说有英特尔在线书籍描述了特定汇编指令所需的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
BTW:这是我发布的代码的网址:http://www.codeproject.com/KB/threads/spinlocks.aspx
答案 0 :(得分:29)
鉴于流水线操作,乱序处理,微代码,多核处理器等,无法保证汇编代码的特定部分将完全占用x个CPU周期/时钟周期/任何周期。
如果存在这样的引用,它将只能在给定特定体系结构的情况下提供广泛的概括,并且根据微代码的实现方式,您可能会发现Pentium M与Core 2 Duo不同,后者与AMD双核等
请注意,本文于2000年更新,并在之前编写过。即使是奔腾4也难以确定指令时序--PIII,PII和原始奔腾更容易,所引用的文本可能基于那些具有更明确指令时序的早期处理器。
现在人们通常使用统计分析来进行代码时序估计。
答案 1 :(得分:21)
其他答案所说的关于无法准确预测在现代CPU上运行的代码的性能是正确的,但这并不意味着延迟是未知的,或者知道它们是无用的。
英特尔和AMD处理器的确切延迟列在Agner Fog's instruction tables中。另请参阅Intel® 64 and IA-32 Architectures Optimization Reference Manual和Instruction latencies and throughput for AMD and Intel x86 processors(来自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适用于奔腾4,它以双倍速度运行芯片的某些组件,允许它以半周期执行操作)
答案 2 :(得分:18)
现代CPU是复杂的野兽,使用pipelining,superscalar execution和out-of-order execution等技术使得性能分析变得困难...... 但并非不可能!
虽然您不能再简单地将指令流的延迟加在一起以获得总运行时间,但您仍然可以(通常)高度准确地分析某些代码(特别是循环)的行为,如上所述以下和其他相关资源。
首先,您需要实际时间。这些因CPU架构而异,但目前x86时序的最佳资源是Agner Fog的instruction tables。这些表覆盖不少于 30个不同的微架构,这些表列出了指令 latency ,这是指令从输入就绪到输出可用的最小/典型时间。用阿格纳的话来说:
延迟:这是指令在a中生成的延迟 依赖链。数字是最小值。缓存未命中, 未对准,例外可能会增加时钟计数 相当。在启用超线程的情况下,使用相同的 另一个线程中的执行单元导致性能较差。 非正规数,NAN和无穷大不会增加延迟。该 使用的时间单位是核心时钟周期,而不是参考时钟周期 由时间戳计数器给出。
因此,例如,add
指令的延迟为一个周期,因此如图所示,一系列依赖添加指令的延迟为每个{{1个周期1}}:
add
请注意,这并不意味着add eax, eax
add eax, eax
add eax, eax
add eax, eax # total latency of 4 cycles for these 4 adds
指令每个只需要1个周期。例如,如果添加指令不依赖,则在现代芯片上,所有4个添加指令都可以在同一周期内独立执行:
add
Agner提供了一个指标,用于捕获一些潜在的并行性,称为互惠吞吐量:
互惠吞吐量:一系列相同类型的独立指令的每条指令的核心时钟周期平均数 在同一个主题中。
对于add eax, eax
add ebx, ebx
add ecx, ecx
add edx, edx # these 4 instructions might all execute, in parallel in a single cycle
,此列为add
,表示每个周期最多可执行4条0.25
条指令(相应的吞吐量为add
)。
倒数吞吐量数字还提示了指令的流水线功能。例如,在最近的x86芯片上,1 / 4 = 0.25
指令的常见形式具有3个周期的延迟,并且内部只有一个执行单元可以处理它们(不像imul
通常具有4个可添加功能单位)。然而,观察到的一系列独立add
指令的吞吐量是1 /周期,而不是每3个周期1个,因为您可能期望延迟为3.原因是imul
单元是流水线的:它可以开始一个新的imul
每个周期,即使之前的乘法还没有完成。
这意味着一系列独立的 imul
指令每个周期最多可以运行1次,但是一系列相关的 imul
指令将会每3个周期仅运行1次(因为下一个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通常会在新架构发布后立即提供新架构的时间安排,但您也可以在imul
和InstLatX86
结果中查看instlatx64类似有组织的时间安排。结果涵盖了许多有趣的旧芯片,而新芯片通常会很快出现。结果大多与Agner一致,但有一些例外。您还可以在此页面上找到内存延迟和其他值。
您甚至可以通过附录C:指令延迟和通过中的IA32 and Intel 64 optimization manual直接从英特尔获取时序结果。我个人更喜欢Agner的版本,因为它们更完整,通常在英特尔手册更新之前到达,并且因为它们提供电子表格和PDF版本而更易于使用。
最后,x86 tag wiki在x86优化方面拥有丰富的资源,包括如何对代码序列进行周期精确分析的其他示例的链接。
如果您想深入了解"数据流分析的类型"如上所述,我建议A Whirlwind Introduction to Data Flow Graphs。
答案 3 :(得分:13)
测量和计算CPU周期在x86上没有任何意义。
首先,问问自己你在计算哪些CPU周期?酷睿2?一个Athlon?奔腾-M?原子?所有这些CPU都执行x86代码,但它们都具有不同的执行时间。执行甚至在同一CPU的不同步进之间变化。
循环计数有意义的最后一个x86是Pentium-Pro。
还要考虑,在CPU内部,大多数指令都被转码为微码并由内部执行单元不按顺序执行,而内部执行单元甚至看起来都不像x86。单CPU指令的性能取决于内部执行单元中可用的资源量。
因此,指令的时间不仅取决于指令本身,还取决于周围的代码。
无论如何:您可以估算不同处理器的吞吐量 - 资源使用情况和指令延迟。相关信息可在英特尔和AMD网站上找到。
Agner Fog在他的网站上有一个非常好的总结。有关延迟,吞吐量和uop计数的信息,请参阅说明表。请参阅microarchictecture PDF以了解如何解释这些。
但请注意xchg
- 带内存没有可预测的性能,即使您只查看一个CPU型号也是如此。即使在L1D缓存中缓存行已经很热的无竞争情况下,作为一个完整的内存屏障意味着它的影响很大程度上取决于加载和存储到周围代码中的其他地址。
顺便说一句 - 因为您的示例代码是一个无锁数据结构基本构建块:您是否考虑过使用编译器内置函数?在win32上,您可以包含intrin.h并使用_InterlockedExchange等功能。
这将为您提供更好的执行时间,因为编译器可以内联指令。内联汇编程序总是强制编译器禁用围绕asm-code的优化。
答案 4 :(得分:6)
锁定xchg eax,dword ptr [edx]
请注意,锁将锁定所有内核的内存提取内存,这在一些多核上可能需要100个周期,并且还需要刷新缓存行。它也会使管道停滞不前。所以我不担心其余的事情。
因此,最佳性能可以回到调整算法关键区域。
关于单个核心的注意事项,您可以通过删除锁来优化此功能,但多核需要它。