所以从我对延迟槽的理解来看,它们发生在调用分支指令时,并且分支之后的下一条指令也从内存中加载。这有什么意义?难道你不希望在分支被占用的情况下,分支后的代码不运行吗?是为了节省时间,以防分行不被占用吗?
我正在看一个管道图,似乎分支后的指令无论如何都要执行。
现在大多数处理器都使用管道。h&p书中的思想和问题无处不在。在撰写这些原创文章时,我会假设实际的硬件与管道的特定概念相匹配。获取,解码,执行,回写。
基本上,流水线是一条装配线,在生产线上有四个主要阶段,所以你一次最多有四个指令要处理。这混淆了执行一条指令需要多少个时钟的概念,它需要多个时钟,但如果你有一些/多个并行执行,那么"平均"可以接近或超过每个时钟。
当你通过一个分支时,装配线失效了。读取和解码阶段的指令必须被丢弃,你必须重新开始填充,所以你需要几个时钟来读取,解码,然后返回执行。分支阴影或延迟槽的概念是恢复其中一个时钟。如果你声明在分支之后的指令总是被执行,那么当一个分支被执行时,解码槽中的指令也会被执行,读取槽中的指令会被丢弃,这样你就有了一个时间洞而不是两个。所以不是执行,空,空,执行,执行你现在有执行,执行,空,执行,执行……在管道的执行阶段。分支减少了50%的痛苦,你的整体平均执行速度提高了,等等。
ARM没有延迟槽,但是通过声明程序计数器在前面两条指令,它也给人一种管道的错觉。任何依赖于程序计数器(pc相对寻址)的操作都必须使用提前两条指令的pc来计算偏移量,对于ARM指令,原始拇指4字节为8字节,当您添加拇指2指令时,它会变得混乱。
这些都是学术界之外的幻想,管道更深,有很多技巧等,为了让遗留代码保持工作,并且/或者不必重新定义指令如何为每个架构变化工作(想象一下mips rev x, 1个延迟槽,rev y 2个延迟槽,rev z 3个槽如果条件a, 2个槽如果条件b, 1个槽如果条件c)处理器继续执行分支后的第一条指令。当它重新填满管道时,会把其他的一把或十几把扔掉。这些管道到底有多深,通常不会与公众分享。
我看到一个关于这是RISC的评论,它可能已经开始了,但是CISC处理器使用相同的技巧,只是给遗留指令集的错觉,有时CISC处理器只不过是一个RISC或VLIW内核,带有一个包装器来模拟遗留CISC指令集(微编码)。
看它是如何制作的。想象一条装配线,生产线上的每一步都有一个任务。如果生产线上的某一步用完了蓝色的东西,而为了生产蓝色和黄色的产品,你需要蓝色的东西。因为有人搞砸了,你一周内都买不到新的蓝色的东西。所以你必须停止生产线,改变每个阶段的供应,并在一段时间内制造红色和绿色产品,通常情况下,这些产品可以在不倾销生产线的情况下适当地分阶段进行。这就像在装配线深处的某个分支发生的事情一样,某些事情导致生产线不得不改变,放弃生产线。延迟槽是一种从不得不在生产线上丢弃的产品中恢复产品的方法。在生产线停止前生产N个产品,而不是每次生产N+1个产品。代码的执行就像生产运行的爆发,在到达分支去另一个短执行路径之前,你经常会得到短的,有时是长的线性执行路径,分支另一个短执行路径……
你不希望在分支被占用的情况下,分支后的代码不运行吗?
但是已经太晚了。CPU管道的全部目的是要在每个周期上完成一条指令。实现这一目标的唯一方法是每个周期获取一条指令。因此,在CPU注意到必须执行分支指令之前,分支指令后的代码已经被获取并正在运行。
这是什么意思?
没有意义。这不是一个功能,它只是这种管道设计的产物。
尽管该指令在执行分支之后出现在程序中,但它实际上是在执行分支之前运行的。查看维基百科关于延迟槽和分支危险的页面。
RISC架构的思想是简化解码并优化管道以提高速度。CPU试图通过流水线来重叠指令的执行,因此同时执行多个指令。
延迟槽的意义在于执行一条已经通过管道一部分的指令,并且现在在一个槽中,否则只能被丢弃。
优化器可以在分支目标处获取第一条指令并将其移动到延迟槽,使其"免费"执行。
该功能没有成为主流,主要是因为世界对现有的ISA1设计进行了标准化,即x86和x86-64,但还有另一个原因。
晶体管数量的二次爆炸使得非常复杂的解码器成为可能。当架构上可见的ISA被转换成微操作时,像延迟槽这样的小技巧就变得不重要了。
<一口> 1。ISA: 指令集体系结构一口>
在流水线实现的教科书示例中,CPU 读取, 解码, 执行, 回写。这些阶段都发生在不同的时钟周期,所以实际上,每个指令在4个周期内完成。然而,当第一个操作码即将被解码时,下一个操作码将从内存加载。当CPU被完全占用时,同时处理4条不同指令的部分,CPU的吞吐量为每个时钟周期一条指令。
当机器码中有一个序列时:
sub r0, #1
bne loop
xxx
处理器可以将信息从sub r0, #1
的回写阶段反馈到bne loop
的执行阶段,但同时xxx已经处于取阶段。为了简化展开管道的必要性,CPU设计者选择使用延迟插槽来代替。延时槽指令取出后,取出单元有分支目标的正确地址。优化编译器很少需要在延迟槽中放置一个NOP,但是在那里插入一个在两个可能的分支目标上都必须需要的指令。