为什么循环指令很慢? 英特尔能不能有效地实现它?

loop递减ecx / rcx,然后在非零时跳转 。 速度很慢,但英特尔不能以低廉的速度让它变得更快? 一个dec和分支uop已经是可能的了(唯一的区别是设置标志)。

从Agner Fog的指令表中 loop使用各种微架构:

  • K8 / K10:7米
  • 推土机家族/ Ryzen:1 m-op(与macros电子testing分支相同的成本,或jecxz

  • P4:4个jecxz (与jecxz相同)

  • P6(PII / PIII):8个
  • Pentium M,Core2:11 uops
  • Nehalem:6个uops。 (11 loope / loopne
  • SnB系列:7个。 (11个为loope / loopne )。 对于jecxz只有2个jecxz
  • Silvermont:7个uops
  • AMD捷豹(低功耗):8个微处理器,5c吞吐量
  • 通过Nano3000:2微软

难道解码器只是解码像lea rcx, [rcx-1] / jrcxz ? 那将是3个微笑。 至less在没有地址大小的前缀的情况下是这样,否则如果跳转被使用,则必须使用ecx并截断RIPEIP 。 也许地址大小控制的减less宽度的奇数select解释了许多微软。

或者更好,只是将它解码为一个融合的十进制分支,不设置标志? jnz上的dec ecx / jnz解码为单个uop(设置标志)。

我知道真正的代码并没有使用它(因为它至lessP5或者其他东西已经很慢了),但是AMD决定让推土机变得很快是值得的。 可能是因为这很容易。


  • SnB系列产品能够快速loop吗? 如果是这样,他们为什么不呢? 如果不是,为什么很难? 很多解码器晶体pipe? 或者在一个融合的十进制和分支uop额外的位来logging它不设置标志? 那些7个微笑可以做什么? 这是一个非常简单的指令。

  • 推土机有什么特别之处,使快速loop容易/值得吗? 还是AMD浪费了一堆晶体pipe来快速制造loop ? 如果是这样,大概有人认为这是一个好主意。


如果loop速度很快 ,那么对于BigInteger任意精度adc循环来说,这是完美的select,以避免部分标志失速/减速(请参阅我对我的回答的评论),或者任何其他情况下要循环而不触及标志。 与dec/jnz相比,它还有一个小小的码尺寸优势。 (和dec/jnz只有SnB家族的macros保险丝)。

在ADC环路中dec/jnz正常的现代CPU上,对于ADCX / ADOX环路(保持OF), loop仍然很好。


它不会阻止我在每个循环都使用loop 16位错误代码的问题上感到恼火,即使他们在循环中也需要另一个计数器。 但至less它不会那么糟糕。

2 Solutions collect form web for “为什么循环指令很慢? 英特尔能不能有效地实现它?”

现在,我写了我的问题 ,我GOOGLE了,它是一个comp.arch ,刚刚出现一个确切的副本。 我期望它很难谷歌(很多“为什么我的循环缓慢”命中),但我第一次尝试( why is the x86 loop instruction slow )得到的结果。

这不是一个好的或完整的答案。

这可能是我们所能得到的最好的结果,除非有人能够更多地了解它,否则就不得不满足。 我没有开始写这个答案作为我自己的问题。


在这个post中有不同理论的好post:

罗伯特

当大量的stream水线开始发生时,在最早的一些机器(大约486)上,LOOP变得缓慢,并且有效地运行最简单的指令在技术上是不切实际的。 所以,LOOP几代人都很慢。 所以没有人使用它。 所以当它变得有可能加速时,就没有真正的动力去做,因为没有人真正使用它。


安东·埃特尔 :

IIRC LOOP在某些软件中用于定时循环; 有一些(重要的)软件不能在LOOP太快的CPU上工作(这是在90年代初左右)。 所以CPU制造商学会了让LOOP变慢。


(保罗,还有其他人:不妨把自己的作品作为自己的答案重新发表,我会从我的答案中删除它,然后向上投票)。

@Paul A. Clayton(偶尔是海报和CPU架构人物) 猜测你如何使用这么多的uops :

我可以想象一个可能的明智的6-μop版本:

 virtual_cc = cc; temp = test (cc); rCX = rCX - temp; // also setting cc cc = temp & cc; // assumes branch handling is not // substantially changed for the sake of LOOP branch cc = virtual_cc 

(请注意,这是6个uops,而不是SnB的7个,而且这个猜测甚至没有考虑SnB性能计数器中已知的任何事情)。

然后说:

我同意一个更短的序列应该是可能的,但是我试图想像一个臃肿的序列,如果允许最小的微架构调整,这可能是有意义的。

总结:devise人员希望通过微码来支持loop ,而不对硬件进行任何调整。

如果一个无用的,只兼容的指令交给微码开发者,他们可能不能或不愿意对内部微架构提出微小的改变来改进这种指令。 他们不但更愿意使用他们的“改变build议资本”,而且build议改变一个无用的情况会降低其他build议的可信度。

Nano背后的build筑师可能已经发现,避免LOOP的特殊shell在面积或功率方面简化了他们的devise。 或者他们可能已经从embedded式用户那里获得了激励来实现快速实现(为了代码密度的好处)。 那些只是野兽的猜测。

如果LOOP的优化超出了其他优化(如比较和分支的融合),将LOOP调整为快速path指令比在微代码中处理LOOP更容易,即使LOOP的性能不重要。

我怀疑这样的决定是基于实施的具体细节。 有关这些细节的信息似乎并不普遍,解释这些信息将超出大多数人的技能水平。 (我不是一个硬件devise师 – 从来没有在电视上玩过或在Holiday Inn Express住过。:-)


然后,线程进入了AMD的境界,吹出了我们一次机会清理x86指令编码的问题。 很难责怪他们,因为每一个变化都是解码器不能共享晶体pipe的情况。 在英特尔采用x86-64之前,还不清楚它会不会成功。 如果AMD64没有赶上,AMD并不想让硬件无人使用。

但是,还是有很多小东西: setcc可能已经变成了32位。 (通常你必须使用xor-zero / test / setcc来避免错误的依赖关系,或者因为你需要一个零扩展的reg)。 Shift可以有无条件的写入标志,即使是零移位计数(移除input数据对eflags的执行variables计数移位的依赖关系)。 最后一次我input这个宠物peeves列表,我认为有第三个…噢, bt / bts等与内存操作数具有地址依赖于索引的高位(位串,不只是位内机器字)。 这些指令对primefaces位字段是非常有用的,并且比他们需要的慢。

请参阅由Dr. Dobb's Journal于1991年3月出版的Abrash,Michael发表的精彩文章(8): http : //archive.gamedev.net/archive/reference/articles/article369.html

文章的总结如下:

优化8088,80286,80386和80486微处理器的代码是很困难的,因为这些芯片使用明显不同的存储器体系结构和指令执行时间。 代码无法针对80×86系列进行优化; 相反,代码必须被devise成在一系列系统上产生良好的性能,或者针对处理器和内存的特定组合进行优化。 程序员必须避免8088支持的exception指令,这些指令在后续的芯片中失去了性能优势。 应使用string说明,但不能依赖。 应该使用寄存器而不是记忆操作。 所有四个处理器的分支也很慢。 内存访问应该alignment以提高性能。 一般来说,优化80486需要与优化8088完全相反的步骤。

通过“8088支持的不寻常的说明”,作者还意味着“循环”:

任何8088程序员都会本能地取代:DEC CX JNZ LOOPTOP:LOOP LOOPTOP,因为LOOP在8088上显着更快。LOOP在286上也更快。然而,在386上,LOOP实际上比DEC / JNZ慢两个周期。 486的钟摆还在继续,LOOP的速度大约是DEC / JNZ的两倍,而且,请注意,我们正在讨论的是808×86指令集中最初的最明显的优化。

这是一篇非常好的文章,我强烈推荐它。 尽pipe它是在1991年出版的,但是今天它的意义非常高。

但是这篇文章只是提供build议,它鼓励testing执行速度并select更快的变体。 它没有解释为什么一些命令变得非常慢,所以它不能完全解决你的问题。

答案是早期的处理器,如80386(1985年发布)和之前的,依次执行指令。

后来的处理器开始使用指令stream水线 – 最初,对804086来说很简单,最后,Pentium Pro(1995年发布)引入了完全不同的内部stream水线,称其为指令被转换为小碎片的“乱序”称为微操作(micro-ops)或微操作(μops)的操作,然后将不同指令的所有微操作放到一个大的微操作池中,只要它们不相互依赖,它们应该同时执行。 这个OOOpipe道原理在现代处理器上仍然使用,几乎没有变化。 您可以在这篇精彩的文章中find关于指令stream水线的更多信息: https : //www.gamedev.net/resources/_/technical/general-programming/a-journey-through-the-cpu-pipeline-r3115

为了简化芯片devise,英特尔决定以这样的方式构build处理器,使得一条指令以非常有效的方式转换成微操作,而另一些则不是。

从指令到微操作的高效转换需要更多的晶体pipe,因此英特尔决定节省晶体pipe,代价是较慢的解码和执行一些“复杂”或“很less使用”的指令。

例如,“英特尔®架构优化参考手册” http://download.intel.com/design/PentiumII/manuals/24512701.pdf提到了以下内容:“避免使用复杂的指令(例如,进入,离开或循环)通常具有超过四个μops并且需要多个周期来解码。 改用简单指令序列。“

因此,英特尔以某种方式决定“循环”指令是“复杂的”,从那以后,它变得非常慢。 但是,英特尔并没有官方的指令说明:每条指令产生多less个微操作,需要多less个周期来解码。

您还可以阅读“英特尔®64和IA-32架构优化参考手册”中有关无序执行引擎的信息:http://www.intel.com/content/dam/www/public/us/en/文档/手册/ 64-ia-32-architectures-optimization-manual.pdf第2.1.2节。

  • execve shellcode写入分段错误
  • 计算机程序运行时会发生什么?
  • 现代C ++编译器的有效优化策略
  • “多核”汇编语言是什么样的?
  • 如何编写x86程序集中的自修改代码
  • “dword ptr”是什么意思?
  • LEA指令的目的是什么?
  • 为什么std :: fill(0)比std :: fill(1)慢?
  • 堆栈上局部variables分配的顺序
  • PHP的x86如何获得> 2 GB文件的文件大小没有外部程序?
  • 如何写一个反汇编?