投机执行会导致昂贵的操作吗?

Will Speculative Execution Follow Into an Expensive Operation?

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

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

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


当前的 x86 设计不会同时推测一个分支的两个ide。他们只推测预测的路径。

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


我不确定当检测到分支预测错误时,在缓存未命中加载或存储已经在执行挂起时,或者 divide正在占用 divide 单元。当然乱序执行不必等待结果,因为没有未来的微指令依赖于它。

在 P4 以外的 uarches 上,ROB/scheduler 中的伪造 uops 在检测到错误预测时会被丢弃。来自 Agner Fog 的微架构文档,谈论 P4 与其他架构:

the misprediction penalty is unusually high for two reasons ... [long pipeline and] ... bogus μops in a mispredicted branch are not discarded before they retire. A misprediction typically involves 45 μops. If these μops are divisions or other time-consuming operations then the misprediction can be extremely costly. Other microprocessors can discard μops as soon as the misprediction is detected so that they don't use execution resources unnecessarily.

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

除了 divider 之外,几乎所有执行单元都是完全流水线化的,因此另一个乘法、洗牌或其他任何东西都可以在不取消飞行中的 FP FMA 的情况下开始。 (Haswell:5 个周期延迟,两个执行单元,每个执行单元每个时钟吞吐量一个,总持续吞吐量为每 0.5c 一个。这意味着最大吞吐量需要同时保持 10 个 FMA,通常有 10 个向量累加器)。不过,Divide 很有趣。整数 divide 是很多微指令,所以分支预测错误至少会停止发出它们。 FP div 只是一条 uop 指令,但不是完全流水线化的,尤其是。在较旧的 CPU 年代。取消占用 divide 单元的 FP div 会很有用,但如果可能的话,IDK。如果添加取消功能会减慢正常情况或消耗更多电量,那么它可能会被排除在外。这是一种罕见的特殊情况,可能不值得在其上花费晶体管。

x87 fsin 之类的东西是非常昂贵的指令的一个很好的例子。直到我回去重新阅读这个问题,我才注意到这一点。它是微编码的,所以即使它有 47-106 个周期的延迟(Intel Haswell),它也是 71-100 微指令。分支预测错误会阻止前端发出剩余的微指令,并取消所有排队的微指令,就像我对整数 division 所说的那样。请注意,真正的 libm 实现通常不使用 fsin 等,因为它们比软件(即使没有 SSE)、IIRC 可以实现的速度更慢且更不准确。


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


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