投机执行是否会进入昂贵的操作

Will Speculative Execution Follow Into an Expensive Operation?

本文关键字:操作 执行 是否 投机      更新时间:2023-10-16

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

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

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


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

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


我不确定在当前的英特尔和AMD设计中,当检测到分支错误预测时,缓存未命中加载或存储已经执行挂起,或者除法占用除法单元时会发生什么。 当然,乱序执行不必等待结果,因为未来的 uops 不依赖于它。

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

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

目前占用执行单元的 UOP 是另一回事:

除器外,几乎所有执行单元都是完全流水线的,因此可以在不取消飞行中 FP FMA 的情况下开始另一个乘法、随机播放或其他任何操作。 (Haswell:5 个周期延迟,两个执行单元,每个执行单元能够为每个时钟吞吐量一个,总持续吞吐量为每 0.5c 一个。 这意味着最大吞吐量需要同时保持 10 个 FMA 在飞行中,通常使用 10 个矢量累加器)。 不过,分裂很有趣。 整数除法是许多 uop,因此分支错误预测至少会停止发布它们。 FPdiv 只是一个 uop 指令,但不是完全流水线的,尤其是在较旧的 CPU 中。 取消捆绑除法单元的 FPdiv 会很有用,但如果可能的话,请取消 IDK。 如果添加取消功能会减慢正常情况的速度,或者消耗更多的电量,那么它可能会被排除在外。 这是一个罕见的特殊情况,可能不值得花晶体管。

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


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


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