据我理解,延迟槽指的是当分支指令被调用时,紧接着分支后面的指令也会从内存中加载。这有什么意义呢?难道我们不期望如果分支被执行,则其后的代码将不会运行吗?这是为了在分支未被执行时节省时间吗?
我正在查看一个流水线图表,似乎分支后面的指令仍然会被执行。
据我理解,延迟槽指的是当分支指令被调用时,紧接着分支后面的指令也会从内存中加载。这有什么意义呢?难道我们不期望如果分支被执行,则其后的代码将不会运行吗?这是为了在分支未被执行时节省时间吗?
我正在查看一个流水线图表,似乎分支后面的指令仍然会被执行。
现在大多数处理器都使用流水线技术。《计算机体系结构:量化研究方法》这本书中提到的思想和问题无处不在。在最初的编写时,我认为实际硬件与流水线技术非常吻合。它的四个主要阶段是取指令(Fetch)、译码(Decode)、执行(Execute)和写回(Write back)。
基本上,流水线就像一条装配线,有四个主要阶段,并且最多可以同时处理四条指令。这也让人困惑于执行一条指令需要多少个时钟周期的概念。虽然执行一条指令需要超过一个时钟周期,但如果有一些或者许多指令在并行执行,那么平均执行速度可以接近或超过每个时钟周期一条指令。
当进行分支操作时,流水线会失败。取指令和译码阶段的指令必须被丢弃,你需要重新开始填充指令,因此你需要耗费一些时钟周期用于取指令、译码,然后才能继续执行。分支延迟插槽的思想就是为了挽回其中一个时钟周期的损失。如果声明分支后的指令总是会执行,则在分支被执行时,译码插槽中的指令也会被执行,取指令插槽中的指令将被丢弃,因此你只需要填充一个时间空白而不是两个。所以,现在流水线执行阶段的顺序是执行、执行、空置、执行、执行……分支的开销减少了50%,整体平均执行速度得到改善等等。
ARM处理器没有延迟插槽,但它同样也通过声明程序计数器向前两条指令的假象来实现流水线技术。任何依赖于程序计数器(pc-relative addressing)的操作都必须使用向前两条指令的pc来计算偏移量。对于ARM指令而言,原始Thumb指令的偏移量为4字节,并且加入Thumb2指令后就变复杂了。
目前在学术领域之外,这些都是假象,管道更深,有很多诀窍等等,以便保持传统代码的工作状态,或者不必为每个架构更改重新定义指令工作方式(想象一下MIPS rev x,1个延迟插槽,rev y 2个延迟插槽,rev z如果条件a为3个插槽并且如果条件b为2个插槽,则处理器继续执行分支后的第一个指令,然后丢弃其他几个指令,因为它重新填充管道。实际上,管道的深度往往不会与公众共享。
我看到有关这是RISC问题的评论,可能起源于此,但CISC处理器使用完全相同的技巧,只是给出传统指令集的错觉,有时CISC处理器不过是具有包装器以模拟遗留CISC指令集(微码化)的RISC或VLIW内核。
观看"How It's Made"节目。想象一条装配线,每个步骤都有一个任务。如果生产线上的某个步骤用完了蓝色小零件,并且为了制造蓝色和黄色产品,您需要蓝色小零件。但由于某个人搞砸了,您在下一个星期内无法获得新的蓝色小零件。因此,您必须停止生产线,更改每个阶段的供应,并暂时制造红色和绿色产品,这通常可以在不倒空生产线的情况下适当引入。这就像在分支中发生的事情,装配线深处的某个地方,某些原因导致生产线必须更改,倒掉生产线。延迟插槽是从生产线上挽回一个产品的方法,使得N+1个产品每次生产运行时出来,而不是N个产品。代码执行就像突发式的生产运行,您经常在进入另一个短执行路径的分支之前获得短线性执行路径,然后再进入另一个短执行路径,再分支...
sub r0, #1
bne loop
xxx
sub r0, #1
的写回阶段的信息反馈到bne loop
的执行阶段,但同时xxxx已经处于取指阶段。为了简化取消流水线的必要性,CPU设计者选择使用延迟槽。在延迟槽中的指令被获取后,取指单元会有正确的分支目标地址。优化编译器很少需要在延迟槽中放置NOP,而是插入一个在两个可能的分支目标中都必须需要的指令。