gol*_*sky 4 cpu cpu-architecture speculative-execution
据我了解,当 CPU 推测性地执行一段代码时,它会在切换到推测性分支之前“备份”寄存器状态,以便如果预测结果错误(使分支无用)——寄存器状态将是安全恢复,而不会破坏“状态”。
所以,我的问题是:推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?
我的意思是,访问 RAM 不是“原子”操作——如果数据当前不在 CPU 缓存中,那么从内存中读取一个简单的操作码可能会导致实际的 RAM 访问,这可能会变成一个非常耗时的操作,从 CPU 的角度来看。
如果在推测分支中确实允许这种访问,它是否仅用于读取操作?因为,我只能假设,如果一个分支被丢弃并执行“回滚”,根据它的大小恢复写操作可能会变得非常缓慢和棘手。而且,可以肯定的是,至少在某种程度上支持读/写操作,因为寄存器本身,在某些 CPU 上,据我所知,物理上位于 CPU 缓存上。
所以,也许更精确的表述是:推测执行的一段代码有什么限制?
Pet*_*des 13
推测性乱序 (OoO) 执行的主要规则是:
OoO exec 通常通过将所有内容视为投机性直到退休来实现。每个加载或存储都可能出错,每个 FP 指令都可能引发 FP 异常。分支是特殊的(与异常相比)只是因为分支错误预测并不罕见,因此处理分支未命中的早期检测和回滚的特殊机制是有帮助的。
是的,可缓存加载可以推测性和 OoO 执行,因为它们没有副作用。
由于存储缓冲区,存储指令也可以推测性地执行。 存储的实际执行只是将地址和数据写入存储缓冲区。 (相关:英特尔硬件上存储缓冲区的大小?究竟什么是存储缓冲区?比这更具技术性,具有更多的 x86 焦点。我认为这个答案适用于大多数 ISA。)
提交到 L1d 缓存发生在存储指令从 ROB 退出后的一段时间,即当已知存储是非推测性的时,相关的存储缓冲区条目“毕业”并有资格提交缓存并变得全局可见。存储缓冲区将执行与其他内核可以看到的任何内容分离,并将该内核与缓存未命中存储隔离,因此即使在有序 CPU 上,它也是一个非常有用的功能。
在存储缓冲区条目“毕业”之前,当回滚错误推测时,它可以与指向它的 ROB 条目一起被丢弃。
(这就是为什么即使是强排序的硬件内存模型仍然允许 StoreLoad 重新排序https://preshing.com/20120930/weak-vs-strong-memory-models/ - 这对于良好的性能几乎是必不可少的,不要让后面的加载等待更早存储实际提交。)
存储缓冲区实际上是一个循环缓冲区:由前端分配的条目(在分配/重命名管道阶段)并在将存储提交到 L1d 缓存时释放。(通过MESI与其他内核保持一致)。
像 x86 这样的强排序内存模型可以通过按顺序从存储缓冲区提交到 L1d 来实现。条目是按程序顺序分配的,因此存储缓冲区基本上可以是硬件中的循环缓冲区。如果存储缓冲区的头部用于尚未准备好的缓存行,则弱序 ISA 可以查看较年轻的条目。
一些国际检索单位(尤其是弱有序)也做合并存储缓冲项,以建立一个单一的8字节的承诺L1D出对32位存储的,例如。
读取可缓存的内存区域被假定为没有副作用,并且可以通过 OoO exec、硬件预取或任何其他方式推测性地完成。错误推测可以“污染”缓存并通过接触真正执行路径不会的缓存行来浪费一些带宽(甚至可能触发 TLB 未命中的推测性页面遍历),但这是唯一的缺点1。
MMIO 区域(其中读取确实有副作用,例如使网卡或 SATA 控制器执行某些操作)需要标记为不可缓存,以便 CPU 知道不允许从该物理地址进行推测性读取。 如果您弄错了,您的系统将不稳定- 我的回答涵盖了您针对推测负载询问的许多相同细节。
高性能 CPU 具有包含多个条目的负载缓冲区,用于跟踪动态负载,包括在 L1d 缓存中未命中的负载。(即使在有序 CPU 上也允许命中未命中和未命中未命中,仅当/当指令尝试读取尚未准备好的加载结果寄存器时才停止)。
在 OoO exec CPU 中,当一个加载地址在另一个加载地址之前准备好时,它还允许 OoO exec。当数据最终到达时,等待来自加载结果的输入的指令准备运行(如果它们的其他输入也准备好)。因此,加载缓冲区条目必须连接到调度程序(在某些 CPU 中称为保留站)。
另请参阅关于 RIDL 漏洞和负载“重放”,了解有关英特尔 CPU 如何专门处理等待的 uops的更多信息,当数据可能从 L2 到达 L2 命中时,积极尝试启动它们。
脚注 1:这个缺点,结合用于检测/读取微架构状态(高速缓存线热或冷)到架构状态(寄存器值)的定时侧通道是启用 Spectre 的原因。( https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)#Mechanism )
了解 Meltdown 对于了解英特尔 CPU 如何选择处理错误抑制的推测性负载的细节非常有用。 http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/
而且,可以肯定的是,支持读/写操作
是的,如果您谈论的是解码为指令 uops 的现代 x86,则通过将它们解码以在逻辑上分离加载/ALU/存储操作。加载像正常加载一样工作,存储将 ALU 结果放入存储缓冲区。所有 3 个操作都可以由乱序后端正常安排,就像您编写单独的指令一样。
如果您的意思是原子RMW,那么这真的不是推测性的。缓存是全局可见的(共享请求可以随时出现)并且无法回滚它(嗯,除了英特尔为事务内存所做的任何事情......)。您绝不能在缓存中放入错误的值。请参阅“int num”的 num++ 可以是原子的吗?有关如何处理原子 RMW 的更多信息,尤其是在现代 x86 上,通过延迟响应以在加载和存储提交之间共享/无效该行的请求。
然而,这并不意味着lock add [rdi], eax
序列化整个管道:加载和存储是唯一被重新排序的指令吗?表明其他独立指令的推测性 OoO exec可能发生在原子 RMW 周围。(相对于像lfence
这样的 exec 屏障会耗尽 ROB 的情况)。
许多RISC ISA仅通过加载链接/存储条件指令提供原子RMW ,而不是单个原子RMW指令。
[读/写操作...],至少在某种程度上,由于寄存器本身,在某些 CPU 上,据我所知,物理上位于 CPU 缓存上。
嗯?错误的前提,这个逻辑没有意义。缓存必须始终正确,因为另一个核心可能随时要求您共享它。与此核心私有的寄存器不同。
寄存器文件像缓存一样由 SRAM 构建,但它们是分开的。板上有一些带有 SRAM存储器(不是高速缓存)的微控制器,并且使用该空间的早期字节对寄存器进行存储器映射。(例如 AVR)。但这些似乎都与乱序执行无关;缓存内存的缓存线绝对不同于用于完全不同的东西的缓存线,例如保存寄存器值。
花费晶体管预算来进行推测执行的高性能 CPU 将缓存与寄存器文件结合起来也不太可能。然后他们会竞争读/写端口。一个具有总读和写端口的大缓存比一个小的快速寄存器文件(许多读/写端口)和一个小的(如 32kiB)L1d 缓存和几个读端口和 1 个写要贵得多(面积和功率)港口。出于同样的原因,我们使用分离式 L1 缓存,并拥有多级缓存,而不是现代 CPU 中每个核心只有一个大型私有缓存。为什么大部分处理器的L1缓存比L2缓存小?
相关阅读/背景:
归档时间: |
|
查看次数: |
518 次 |
最近记录: |