【问题标题】:What happens with nested branches and speculative execution?嵌套分支和推测执行会发生什么?
【发布时间】:2020-03-31 05:48:04
【问题描述】:

好的,所以我知道如果一个特定的条件分支有一个需要时间来计算的条件(例如内存访问),CPU 会假设一个条件结果并沿着该路径推测性地执行。但是,如果沿着这条路径弹出另一个缓慢的条件分支会发生什么(当然,假设第一个条件尚未解决并且 CPU 不能只提交更改)? CPU只是在推测里面推测吗?如果最后一个条件被错误预测但第一个条件不是,会发生什么?它只是一路回滚吗?

我说的是这样的事情:

if (value_in_memory == y){
   // computations
   if (another_val_memory == x){
      //computations
   }
}

【问题讨论】:

  • 写一个我可能会完成的答案,但重点是 everything 总是被视为推测性的,因为任何加载或存储都可能出错,或者 ALU 划分可能会陷入困境除异常等。因此,一次飞行中的 2 个分支实际上并不特别。通过快速恢复,可以更快地发现分支错误推测,并在其他推测仍在进行中时回滚到分支完成之前。见What exactly happens when a skylake CPU mispredicts a branch?
  • @PeterCordes 所以即使是“常规”指令在提交之前也是推测性地执行的,它们之间的唯一区别是人为的区别,而不是计算机的区别?那么,我假设 CPU 存储了多个可能的回滚点?例如,如果我有可能导致页面错误的加载指令或只是使用过时的值,在条件分支中,CPU 会识别这些指令和场景并为每个指令和场景保存一个状态?我觉得我理解错了,因为这可能会导致大量存储寄存器状态和复杂的依赖关系......

标签: cpu-architecture nested-if branch-prediction speculative-execution


【解决方案1】:

推测执行是执行的常规状态,而不是乱序 CPU 在看到分支时进入,然后在分支不再运行时离开的特殊模式。

如果您认为不仅分支会出错,而且许多指令(包括访问内存的指令)对其输入值都有限制等,这将更容易看出。因此,任何实质性的乱序执行都意味着不断的推测, CPU 就是围绕这个想法构建的。

所以“嵌套分支”在这个意义上并没有特别之处。

现在,现代 CPU 有多种方法可以快速恢复分支预测错误,比从其他类型的故障中恢复要快1。例如,他们可能会在某些分支处对寄存器映射的状态进行快照,以允许在分支位于重新排序缓冲区的头部之前开始恢复。由于在所有个分支上进行快照并不总是可行的,因此可能会涉及复杂的启发式方法来决定在哪里拍摄快照。

我提到最后一部分是因为它是嵌套分支可能很重要的一种方式:当有大量分支在运行时,您可能会遇到一些与跟踪这些分支以进行恢复目的相关的微架构限制。更详细的可以看一下“branch order buffer”的专利(Intel的技术,当然还有其他的)。


1 基本的恢复方法是继续执行,直到故障指令下一个退出,然后丢弃所有较年轻的指令。在分支错误预测的情况下,这意味着您实际上可能会遭受两个或更多错误预测,只有最旧的错误预测实际生效:例如,一个较年轻的分支错误预测,并且在执行到该分支时(此时可能会发生恢复),另一个发生错误预测,因此年轻的最终被丢弃。

【讨论】:

  • 我认为 BoB 必须对整个 RAT(寄存器分配表)进行快照以实现快速恢复,而不仅仅是哪些物理寄存器保持当前架构状态。 What exactly happens when a skylake CPU mispredicts a branch?。快速恢复的好处是独立工作的乱序执行之前分支可以在回滚发生时继续(到通过执行分支检测到的正确路径)。但是,如果一个 40 个条目的 BoB 真的是 RAT 的 40 倍,那么 IDK 将是一个很大的状态。大概有什么诡计。
  • @PeterCordes - 是的,它可能部分是语义。 Something 需要跟踪它,因为在某些时候需要释放物理寄存器,但它不一定是 RAT,其基本工作只是跟踪 current i> 映射以支持重命名。由于 RAT 是关键的单周期重命名循环中高度移植的东西,因此保持它尽可能紧凑是有意义的。可以在 PRRT(退休后回收表)之类的东西中跟踪完全映射,这可能会更慢,因为退休一条指令实际上可能需要几个 ...
  • ... 周期(嗯,从逻辑上讲,指令在一个周期内退出,但在所有资源完全释放之前可能会发生额外的工作)。尽管如此,您的观点仍然存在 - 无论该跟踪发生在 RAT 本身,还是其他可能属于或不属于 RAT 的地方,都必须恢复该信息,因此我将编辑答案。
  • 从 2000 年开始阅读英特尔的专利(虽然很旧,但我假设它适用于现代 CPU,因为可能只有在 2018 年发现 Spectre 和 Meltdown 时才进行了重大更改)他们确实提到他们使用多个影子 RAT 以在多个分支的情况下存储多个状态。据我所知,他们只是在分支出现时填充 RAT,并在所有 RAT 都填满时停止这样做,确保一个 RAT 保持最旧(未提交)的分支状态,但继续推测执行(不保存更多快照)。谢谢两位的回答!
  • @C.Pinto - 是的,我还研究了专利和一点,得出了类似的结论。我不确定的一件事是“它们只是在分支出现时填充 RAT,并在所有 RAT 都被填充时停止这样做”,因为在某些常见情况下它的性能似乎很差:例如,有时不可预测的分支在一个循环中(具有非常可预测的循环分支)。如果你把所有的快照都浪费在可预测的分支上,你会在不可预测的分支上做得更糟。所以我想我记得读过一些暗示...
【解决方案2】:

(也许不是一个完整的答案,但是当@BeeOnRope 发布答案时我已经写了一些。无论如何发布这个以获得更多链接和技术细节,以防有人好奇。)


一切总是推测性的,直到它退休并成为非推测性的,肯定发生的,架构状态的一部分。

例如任何 加载可能会因地址错误而出错,任何div 可能会陷入除以零。另请参阅 Out-of-order execution vs. speculative executionWhat exactly happens when a skylake CPU mispredicts a branch? 提到 branch 错误预测 被特殊处理,因为它们预计会很频繁。快速恢复可以在错误预测的分支达到退休之前开始,这与例如故障负载的行为不同。 (这就是 Meltdown 可被利用的部分原因。)

所以即使是“常规”指令在提交之前也是推测性地执行的,它们之间的唯一区别是人为的区别,而不是计算机的区别?那么,我假设 CPU 存储了多个可能的回滚点?例如,如果我有可能导致页面错误的加载指令或只是使用过时的值,在条件分支中,CPU 会识别这些指令和场景并为它们中的每一个保存一个状态?我觉得我理解错了,因为这可能会导致大量存储寄存器状态和复杂的依赖关系。

退休状态始终是一致的,因此您可以随时回滚到那里并丢弃所有正在进行的工作,例如如果外部中断到达,您希望处理它,而无需等待一连串缓存未命中加载全部执行。 When an interrupt occurs, what happens to instructions in the pipeline?

这种跟踪基本上是免费发生的,或者是您无论如何都需要做的事情才能检测到哪个指令出错,而不仅仅是某处出现问题。 (这称为“精确异常”)

人类可以有效做出的真正区别是在执行非错误案例期间很有可能出错的推测。如果您的代码得到一个错误的指针,那么它的执行方式并不重要;它会出现页面错误,与本地 OoO 执行详细信息相比,这将非常慢。


您说的是现代无序 (OoO) 执行(不仅仅是获取)CPU,例如现代 Intel 或 AMD x86、高端 ARM、MIPS r10000 等。

前端是有序的(沿着预测路径进行推测),从无序后端到非推测性退休状态的提交(也称为退休)也是如此。 (又名已知良好的架构状态)。

CPU 使用两种主要结构来跟踪后端的指令(或在 x86 上,uops = 部分指令)。前端的最后阶段(在获取/解码之后)分配/重命名指令并同时将它们添加到both这些结构中。

  • RS = 预留站 = 调度程序:尚未-执行指令,等待执行单元。 RS 跟踪依赖关系并将最旧的就绪微指令发送到就绪的执行单元。
  • ROB = ReOrder Buffer: not-yet-retired 指令。指令按顺序进入和离开,因此它可以只是一个循环缓冲区。

    包含一个标志来标记每个条目是否已执行,一旦 RS 将其发送到报告成功的执行单元就设置。 ROB 中所有已设置完成执行位的最旧指令都可以“退出”。

    还包括一个标志,指示“如果达到退役则有故障”。例如,这避免了花费时间处理来自错误执行路径上的加载指令的页面错误(很可能有指向未映射页面的指针)。要么在分支错误预测的阴影下,要么就在另一条本应首先出错但 OoO exec 稍后处理的指令之后(按程序顺序)。

(我也忽略了寄存器重命名到一个大型物理寄存器文件。 这就是“重命名”部分。分配包括选择指令将使用哪个执行端口,以及为内存指令保留加载或存储缓冲区条目。)

(还有一个存储缓冲区;存储不直接写入 L1d 缓存,而是写入存储缓冲区。这使得可以推测性地执行存储并在不让其他内核看到它们的情况下仍然回滚。它也将缓存未命中存储与执行分离。一旦存储指令退出,存储缓冲区条目“毕业”并有资格提交到 L1d 缓存,一旦 MESI 获得对缓存行的独占访问权,并且一旦满足内存排序规则。 )


执行单元检测指令是否应该出错,或者是否被错误推测并应该回滚,但在指令达到退休之前不一定会采取行动。

按顺序退出是在 OoO exec 之后恢复程序顺序的步骤,包括错误推测异常的情况。


术语:当指令从前端发送到 ROB + RS 时,英特尔将其称为“问题”。其他计算机体系结构的人通常称之为“调度”。

将微指令从 RS 发送到执行单元被英特尔称为“调度”,其他人称为“发布”。

【讨论】:

    猜你喜欢
    • 2014-07-03
    • 2012-02-03
    • 2011-08-15
    • 2021-10-01
    • 2022-01-02
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多