x86 - 这些年来,英特尔为何改变静态分支预测机制?

标签 x86 compiler-construction intel cpu-architecture branch-prediction

here我知道英特尔近年来实现了几种静态分支预测机制:


80486年龄:永远不会被接受
奔腾4年龄:后退/未前进
像Ivy Bridge,Haswell这样的较新CPU已变得越来越无形,请参见Matt G's experiment here


而且英特尔似乎不想再谈论它了,因为我在英特尔文档中找到的最新材料是大约十年前写的。

我知道静态分支预测远不及动态分支预测重要,但是在很多情况下,CPU将会完全丢失,并且程序员(带有编译器)通常是最佳指南。当然,这些情况通常不是性能瓶颈,因为一旦频繁执行分支,动态预测器就会捕获它。

由于英特尔不再在其文档中明确声明动态预测机制,因此GCC的builtin_expect()除了从热路径中删除不太可能的分支外,无能为力。

我对CPU设计并不熟悉,我也不知道英特尔如今使用哪种静态预测器确切的机制,但是我仍然觉得英特尔的最佳机制应该是清楚地记录他的CPU,“当动态预测器发生故障时我打算去哪里? ,前进或后退”,因为通常程序员是当时的最佳指南。

更新:我发现您提到的主题逐渐超出了我的了解。这里涉及一些动态预测机制和CPU内部细节,而我在两到三天内无法学习。因此,请允许我暂时退出您的讨论并重新充电。这里仍然欢迎任何答案,也许会帮助更多人

最佳答案

静态预测在现代设计中不受欢迎(甚至可能不存在)的主要原因是,与动态预测相比,静态预测在管道中发生得太晚了。基本问题是在提取分支方向和目标位置之前必须先知道它们,但是静态预测只能在解码之后(在提取之后进行)进行。

更详细地...

CPU流水线

简而言之,在执行期间需要从内存中获取指令,对这些指令进行解码,然后执行它们1。在高性能CPU上,这些阶段将进行流水线处理,这意味着它们通常都将并行发生-但在任何给定时刻都需要不同的指令。您可以阅读有关on Wikipedia的内容,但是请记住,现代CPU更复杂,通常具有更多的阶段。

在现代的x86上,使用复杂的变长指令集,可能仅在获取和解码指令中涉及许多流水线“阶段”,可能是六个或更多。这些指令也是superscalar,能够一次执行多个指令。这意味着当以最高效率执行时,将有许多指令处于飞行中,处于获取,解码,执行等各个阶段。

重定向获取

分支分支的效果在管道的整个初始部分(通常称为前端)上都可以感受到:跳转到新地址时,需要从该新地址获取,从该新地址解码等。我们说采取分支需要重定向获取。这对分支预测可以使用以有效执行的信息施加了某些限制。

考虑静态预测的工作原理:它查看指令,如果它是分支,则比较其目标以查看它是“向前”还是“向后”。所有这些必须在很大程度上发生在解码发生之后,因为那是知道实际指令的时间。但是,如果检测到分支并预测采取分支(例如,向后跳转),则预测器需要重定向取回,这是较早的流水线阶段。到在解码指令N之后重新定向取回的时间时,已经有许多后续指令在错误(未采用)的路径上被取回和解码。这些必须扔掉。我们说在前端引入了气泡。

所有这一切的结果是,即使静态预测是100%正确的,但在采用分支的情况下,效率很低,因为前端流水线被取消了。如果在获取和解码结束之间有6个流水线级,则每个假定分支都会在流水线中引起6周期的冒泡,并且有一个很大的假设,即预测本身和清除不良路径指令的时间为“零周期”。

救援动态预测

但是,现代x86 CPU能够每个周期最多执行1个分支,即使对于完美预测的静态执行,它也比限制要好得多。为此,预测器通常无法使用解码后可用的信息。它必须能够重定向每个周期的提取,并且仅使用在最后一次预测后延迟一个周期的可用输入。本质上,这意味着预测器基本上是一个自包含的过程,仅将其自身的输出用作下一个周期的预测的输入。

这是大多数CPU上的动态预测器。它预测从下一个周期取回的位置,然后根据该预测来预测此后从该周期取回的位置,依此类推。它不使用有关已解码指令的任何信息,而仅使用分支的过去行为。它最终确实会从执行单元获得有关分支的实际方向的反馈,并根据分支的实际方向更新其预测,但是在相关指令通过预测器后的许多周期内,这基本上都是异步发生的。

加起来

所有这些都可以消除静态预测的用处。

首先,预测来得太迟了,因此,即使工作正常,也意味着在现代Intel上会出现6-8个周期的泡沫(实际上,这些是观察到的来自Intel所谓的“前端恢复者”的数据)。这极大地改变了成本/收益等式,根本无法做出预测。当您在获取预测之前具有动态预测器时,您或多或少希望进行一些预测,并且即使预测具有51%的准确性,也可能会有所收获。

但是,对于静态预测,如果要进行“实际”预测,则需要具有较高的准确性。例如,考虑一个8周期的前端恢复成本,而一个16周期的“完全错误预测”成本。假设在某些程序中,向后冷分支的获取频率是不向后分支的两倍。这应该是静态分支预测的胜利,它可以预测反向采用,对(相对于始终“预测” 2不采用的默认策略)?

没那么快!如果您假设8个周期的重新控制成本和16个周期的完全错误预测成本,那么它们最终将具有10.67个周期的相同混合成本-因为即使在正确预测的情况下,也存在8个周期的泡沫,但在静态情况下,没有相应的成本。

加上无静态预测的情况已经使静态预测的另一半正确了(前分支未采用的情况),静态预测的效用并不像人们想象的那么大。

为什么现在要更改?也许是因为管道的前端部分比其他部分更长,或者是因为动态预测器的性能和存储能力的提高意味着根本没有资格进行静态预测的冷支。改善静态预测器的性能还意味着,对于冷分支,反向预测的强度会降低,因为动态预测器会更频繁地记住循环(这是反向采样规则的原因)。

节省动态预测资源

这种变化也可能是由于与动态预测的交互作用:动态预测器的一种设计是根本不使用从未发现采用的分支的任何分支预测资源。由于此类分支是通用的,因此可以节省很多历史表和BTB空间。但是,这样的方案与静态预测器不一致,该静态预测器将反向分支预测为已采取:如果从不采用反向分支,则您不希望静态预测器选择该分支并将其预测为已采取,从而弄乱了您的行为节省未使用分支机构资源的策略。



1 ...然后还要做更多的事情,例如退休,但是-执行后发生的大部分情况对于我们这里的目的并不重要。

2我在这里用惊吓语录了“预测”,因为在某种程度上它甚至都不是预测:在没有任何相反预测的情况下,fetch和解码的默认行为是不采取,所以如果不这样做,它就是你得到的完全不进行任何静态预测,而动态预测器则不会告诉您。

关于x86 - 这些年来,英特尔为何改变静态分支预测机制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51822731/

相关文章:

assembly - 如何使用 gcc 生成 Intel 语法的汇编代码?

vba - 如何检测计算机是 32 位还是 64 位?

linux - 如何将 readline 库链接到目标文件?

c - Varnish 守护进程未启动 : Resource temporarily unavailable

ruby - 在不同变量范围内的表现?

C++方法返回指向抽象类的指针,需要使用子类中的方法

c++ - 保证内存排序和正确的编程实践

android - HAXM在最新的SDK更新后消失了

python - 如何在我的 python 代码中为 Pytorch (IPEX) 启用英特尔扩展?

assembly - 汇编中的相对vs绝对jmp