11

如果我正确理解分支(x86),处理器有时会推测性地采用代码路径并执行指令并“取消”错误路径的结果。如果错误代码路径中的操作非常昂贵,例如导致缓存未命中的内存读取或一些昂贵的数学运算,该怎么办?处理器会尝试提前执行一些昂贵的事情吗?处理器通常如何处理这个问题?

if (likely) {
    // do something lightweight (addition, subtraction, etc.)
} else {
    // do something expensive (cache-miss, division, sin/cos/tan etc.)
}
4

1 回答 1

7

tl:dr:影响并没有你想象的那么糟糕,因为 CPU 不再需要等待缓慢的事情,即使它没有取消它们。几乎所有东西都经过大量流水线处理,因此可以同时进行许多操作。错误推测的操作不会阻止新操作的启动。


当前的 x86 设计不会同时在分支的两侧进行推测。他们只推测预测的路径。

我不知道任何特定的微架构在任何情况下都会沿着分支的两种方式进行推测,但这并不意味着没有。我大多只阅读微架构(请参阅标签 wiki 以获取指向 Agner Fog 的微架构 gude 的链接)。我敢肯定,它已在学术论文中提出,甚至可能在某处的实际设计中实现。


我不确定当前的 Intel 和 AMD 设计在缓存未命中加载或存储已经在执行挂起时检测到分支错误预测时会发生什么,或者一个划分正在占用划分单元。当然乱序执行不必等待结果,因为没有未来的微指令依赖它。

在 P4 以外的 uarch 上,当检测到错误预测时,ROB/调度程序中的虚假微指令将被丢弃。来自 Agner Fog 的 microarch 文档,谈论 P4 与其他 uarches:

错误预测的惩罚异常高有两个原因……[长管道和]……错误预测分支中的虚假 μop 在退休之前不会被丢弃。错误预测通常涉及 45 μops。如果这些微操作是除法或其他耗时的操作,那么错误预测的代价可能会非常高。其他微处理器可以在检测到错误预测后立即丢弃 μop,这样它们就不会不必要地使用执行资源。

目前占据执行单元的微指令是另一回事:

除了除法器之外,几乎所有执行单元都是完全流水线的,因此另一个乘法、随机播放或其他任何操作都可以在不取消飞行中的 FP FMA 的情况下启动。(Haswell:5 个周期延迟,两个执行单元每个时钟吞吐量一个,总持续吞吐量为每 0.5c 一个。这意味着最大吞吐量需要同时保持 10 个 FMA 处于运行状态,通常使用 10 个向量累加器)。不过,分歧很有趣。整数除法是许多 uops,因此分支错误预测至少会停止发布它们。FP div 只是一条 uop 指令,但不是完全流水线的,尤其是。在较旧的 CPU 中。取消占用除法单元的 FP div 会很有用,但如果可能的话,请使用 IDK。如果添加取消功能会减慢正常情况,或者消耗更多功率,那么它可能会被排除在外。它'

x87fsin或其他东西是非常昂贵的指令的一个很好的例子。直到我回去重新阅读问题时,我才注意到这一点。它是微编码的,因此即使它的延迟为 47-106 个周期(英特尔 Haswell),它也是 71-100 微秒。分支错误预测将阻止前端发出剩余的微指令,并取消所有排队的微指令,就像我所说的整数除法一样。请注意,实际libm实现通常不使用fsin等,因为它们比软件(即使没有 SSE)IIRC 中可以实现的更慢且更不准确。


对于缓存未命中,它可能会被取消,从而可能节省 L3 缓存(可能还有主内存)中的带宽。即使没有,指令也不再需要退出,因此 ROB 不会在等待它完成时填满。这通常是缓存未命中对 OOO 执行造成如此大的伤害的原因,但在这里最坏的情况是占用了加载或存储缓冲区。现代 CPU 可能同时有许多未命中的缓存未命中。通常代码无法实现这一点,因为未来的操作取决于缓存中丢失的加载结果(例如,在链表或树中追逐指针),因此多个内存操作无法流水线化。即使分支错误预测不会取消大部分正在进行的内存操作,它也避免了大多数最坏的影响。


我听说ud2在代码块的末尾放置一条(非法指令)以阻止指令预取在该块位于页面末尾时触发 TLB 未命中。我不确定何时需要这种技术。也许如果有一个总是实际采用的条件分支?这没有意义,您只需使用无条件分支。一定有什么我不记得你什么时候这样做的。

于 2016-03-08T02:43:31.577 回答