推测执行会转化为昂贵的操作吗?

11

如果我正确理解分支(x86),处理器有时会猜测性地采取代码路径并执行指令,然后“取消”错误路径的结果。如果错误代码路径中的操作非常昂贵,例如导致缓存未命中或某些昂贵数学运算的内存读取,处理器会怎样?处理器通常如何处理这种情况?

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

据我所知,无论是在执行规范还是非规范的情况下,它都会按照一系列指令流程来执行。实际上,我认为执行代码是否是在进行推测执行并不会传递到执行单元。只有在之后(退役单元)才会决定哪些需要退役,哪些需要丢弃。 - Jerry Coffin
@JerryCoffin,我不明白指令被执行但未被翻译到执行单元的含义,您能换一种说法吗?您是指推测性指令不占用任何CPU周期吗? - user1043761
2
请注意,分支预测和投机执行是两种不同的技术。标题提到了分支预测,而问题正文则谈到了投机执行。同时,那些被投机执行的指令当然会消耗CPU执行单元的资源。 - void_ptr
@void_ptr 噢,抱歉,这里的礼仪是什么,我应该编辑标题吗? - user1043761
@user1043761:是的,编辑标题和/或问题正文以使其成为更好的帖子,只要您不将其改为不同的问题,从而使答案无效。在这种情况下,只需更改标题即可。 - Peter Cordes
1
@PeterCordes - Anders Fogh 给大家泼了一盆冷水:负面结果:从用户模式读取内核内存。据我所知,推测执行和博客导致了Meltdown和Spectre漏洞。 - jww
1个回答

7
简短概述: 影响并不像你想的那么糟糕,因为CPU不再需要等待缓慢的操作,即使它们没有被取消。几乎所有的操作都被密集地分段处理,因此许多操作可以同时进行。错误预测的操作并不会阻止新的操作开始。

当前的x86设计不会同时对分支的两侧进行猜测。它们只会沿着预测的路径进行猜测。

我不知道是否有任何特定的微架构在任何情况下都可以同时猜测分支的两个方向,但这并不意味着没有。我主要只了解微架构(请参阅标签wiki以获取链接到Agner Fog的微架构指南)。我相信这已经在学术论文中被提出,并且可能在某个实际设计中得到了实现。


我不确定在当前的Intel和AMD设计中,当检测到分支预测错误时,缓存未命中的加载或存储已经执行挂起,或者除法占用了除法单元会发生什么。当然,乱序执行不必等待结果,因为没有未来的uop依赖于它。
在P4以外的微架构中,当检测到分支预测错误时,ROB /调度器中的虚假uops将被丢弃。从Agner Fog的微架构文档中可以看出,他谈到了P4与其他微架构之间的区别:
“误判惩罚异常高,有两个原因...[长流水线和]...在未正确预测分支之前,虚假μops不会被丢弃。一个误判通常涉及45个μops。如果这些μops是除法或其他耗时操作,则误判可能非常昂贵。其他微处理器可以在检测到误判后立即丢弃μops,以便它们不会不必要地使用执行资源。”
目前占用执行单元的uops则另当别论:
几乎所有执行单元(除了除法器)都是完全流水线化的,因此另一个乘法、洗牌或其他操作可以在不取消正在进行的FP FMA的情况下启动。 (Haswell:5个时钟周期的延迟,每个具有每个时钟周期吞吐量的两个执行单元,总持续吞吐量为每0.5c一个。这意味着最大吞吐量需要同时保持10个FMAs,并通常带有10个矢量累加器)。 但是除法很有趣。 整数除法是许多uop,因此分支错误预测至少会停止发出它们。 FP除法仅是单个uop指令,但不完全流水线化,尤其是在较旧的CPU中。 取消绑定除法单元的FP div会很有用,但我不知道是否可能。 如果添加取消功能会使正常情况变慢或成本更高,则可能会被省略。 这是一个罕见的特殊情况,可能不值得花费晶体管。

x87中的fsin指令是一个非常昂贵的例子。直到我重新阅读这个问题,我才注意到这一点。它是微编码的,因此即使它具有47-106个周期的延迟(Intel Haswell),它也有71-100个uop。分支预测失败会导致前端无法发布其余的uop,并取消排队的所有uop,就像整数除法一样。请注意,真正的libm实现通常不使用fsin等指令,因为它们比软件实现(即使没有SSE)更慢、不够准确。


对于缓存未命中,它可能会被取消,从而在L3缓存(或者主内存)上节省带宽。即使没有取消,指令也不再需要退役,因此ROB不会因等待其完成而填满。这通常是为什么缓存未命中会对OOO执行造成如此大的影响,但在这里,最坏的情况只是占用了一个负载或存储缓冲区。现代CPU可以同时有多个未完成的缓存未命中操作。通常,代码无法实现这一点,因为未来的操作依赖于在缓存中未命中的加载的结果(例如,在链接列表或树中跟踪指针),因此无法将多个内存操作进行流水线处理。即使分支预测错误没有取消大量正在进行的内存操作,也可以避免大部分最坏的影响。
我听说在代码块的末尾放置一个ud2(非法指令),以防止指令预取触发TLB未命中,当该块位于页面末尾时。 我不确定何时需要使用此技术。也许如果有条件分支始终实际被采用?这没有意义,你应该只使用无条件分支。可能有一些我忘记了的事情。

分支预测不是通常在编译步骤中完成的吗(更具体地说,是优化)?还是x86架构本身有某些固有的能力可以准确执行分支预测? - Qix - MONICA WAS MISTREATED
4
@Qix 不,分支预测完全是硬件的事情。 - Mysticial
2
@Qix:你可以向编译器暗示分支通常走哪种方式,这会影响其代码布局决策(因此快速路径大多是未执行的分支,即使在正确预测时也略微更好。尤其是对于I-cache中的代码密度而言:在函数中跳来跳去通常需要拉取更多的代码缓存行)。P4有分支预测提示,但所有其他uarches都会忽略它们。请参见https://dev59.com/QXI-5IYBdhLWcg3wcn7m#1851445。 - Peter Cordes
2
很好的回答。如果发生页面错误(例如在误判循环退出并超出某个页面边界时),内存访问也可能具有“重”副作用,但由于这些操作处于清除分支的阴影下,它们永远不会到达提交点并触发故障。顺便说一句,关于错误路径消耗能量的问题有一个完整的讨论,一些研究正在避免对难以预测的分支进行推测(不过你需要预测该属性 :)。 - Leeor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接