现代设计中为什么不喜欢静态预测,甚至可能不存在静态预测,主要原因是与动态预测相比,静态预测发生得太晚了。基本问题在于,在获取它们之前必须知道分支方向和目标位置,但静态预测只能在解码后(在获取之后)进行。
更详细地说...
CPU流水线
简而言之,在执行期间需要从内存中获取指令,对这些指令进行解码,然后执行它们。在高性能CPU上,这些阶段将被管道化,这意味着它们通常都会并行进行-但对于任何给定时刻的不同指令。您可以在
维基百科上阅读一些关于此的内容,但请记住,现代CPU更加复杂,通常具有许多更多的阶段。
在现代x86上,由于复杂的可变长指令集而涉及许多管道“阶段”仅用于获取和解码指令,可能有半打或更多个。这样的指令也是
超标量的,能够同时执行多条指令。这意味着在执行时,将有许多指令正在飞行,处于获取、解码、执行等各个阶段。
重定向获取
当一个分支被执行时,其影响将在流水线的整个初始部分(通常称为前端)中感知:当你跳转到一个新地址时,需要从该新地址获取、解码等。我们说,一个已执行的分支需要“重定向取指”。这对分支预测可以使用的信息施加了一定的限制,以使其能够高效执行。
考虑静态预测的工作原理:它查看指令,如果是分支,则比较其目标地址以确定其是“向前”还是“向后”。所有这些都必须在大部分解码发生之后才能进行,因为那时才知道实际指令。然而,如果检测到一个分支并且预测其为已执行(例如,向后跳转),则预测器需要重定向取指,这要早得多。在解码指令N之后重定向取指时,已经有许多后续指令在错误的(未执行)路径上被获取和解码。这些必须被丢弃。我们说,在前端引入了一个“气泡”。
总之,即使静态预测是100%正确的,在已执行分支的情况下,其效率也非常低下,因为前端流水线被破坏了。如果在取指和解码结束之间有6个流水线阶段,那么每个已执行的分支都会在流水线中引入一个6个周期的气泡,这是基于预测本身和清除错误路径指令需要“零周期”的慷慨假设。
动态预测挽救了这一局面。
现代x86 CPU能够在每个周期执行一次分支,比静态预测的限制好得多。为了实现这一点,预测器通常不能使用解码后可用的信息,必须能够每个周期重定向抓取,并且仅使用上一个预测后一个周期才能获得的输入。基本上,这意味着预测器是一个完全自包含的流程,仅使用其自己的输出作为下一个周期预测的输入。
这就是大多数CPU上的动态预测器。它预测下一个周期从哪里获取,然后根据该预测预测下一个周期从哪里获取,依此类推。它不使用任何有关解码指令的信息,而只使用分支的过去行为。它最终会从执行单元中获得反馈,关于分支的实际方向,并根据此更新其预测,但这些都是异步发生的,许多周期后才通过预测器。
所有这些都有助于削弱静态预测的有用性。首先,预测来得太晚,因此即使在完美工作时,对于Intel现代处理器的taken分支(确实是Intel所谓的“前端重定向”观察到的数字)也会产生6-8个周期的气泡。这极大地改变了做出任何预测的成本/效益方程。当您在抓取之前有一个动态预测器时,您想要进行一些预测,即使它只有51%的准确率,也可能会得到回报。
对于静态预测,如果你想进行“取出”预测,就需要高准确度。例如,在某个程序中,冷退回分支被采取的频率是不采取的两倍。这应该是静态分支预测的胜利,预测退回到正确状态(与默认策略相比,总是预测为未采取)。但要注意的是,如果假设重新转向成本为8个周期,全面错误预测成本为16个周期,则它们最终具有相同的混合代价,即10.67个周期。此外,不使用静态预测的情况已经将静态预测的另一半正确性(前向分支不采取的情况)得到了,所以静态预测的效用并不像人们想象的那么大。
为什么现在改变呢?也许是因为管道前端的部分长度与其他部分相比更长,或者因为动态预测器的性能和内存提高了,意味着更少的冷分支符合静态预测。改进静态预测器的性能还意味着对于冷分支而言,退回采取预测的力量会变得较弱,因为循环(这是退回采取规则的原因)更容易被动态预测器记住。
节省动态预测资源
这种变化可能是由于与动态预测的交互作用导致的:对于一个动态预测器的设计,不使用任何分支预测资源来处理从未被观察到的分支。由于这样的分支很常见,可以节省大量历史表和
BTB空间。然而,这种方案与静态预测器不一致,因为静态预测器会将向后分支预测为已经被采取:如果向后分支从未被采取,您不希望静态预测器选择这个分支,并将其预测为已采取,从而破坏了节省资源以处理未采取分支的策略。
1 ...然后做更多的事情,比如退休,但是执行之后大部分内容对我们在这里的目的并不重要。
2 我在这里加了“预测”的引号,因为从某种意义上来说,这甚至不算是预测:如果没有任何相反的预测,取指和译码的默认行为就是不取,所以如果你根本没有输入任何静态预测,并且你的动态预测也没有告诉你相反的结果,那么你得到的就是默认的结果。
call +0
那样有趣。 - fuzcall +0
对于返回地址预测器是特殊处理的。它是否也被特殊处理为前端甚至不是跳转,只是一个push rip
?这是可以测试的:如果正确,交替使用jmp +0
/call +0
可以以2 IPC运行。(偶尔重置RSP以获得L1d命中,以避免存储带宽瓶颈。) - Peter Cordes