以推测方式执行的CPU分支可以包含访问RAM的操作码吗?

问题描述

据我了解,当cpu以推测方式执行一段代码时,它会在切换到推测分支之前“备份”寄存器状态,因此,如果预测结果错误(使分支无用),则该寄存器状态可以安全地恢复,而不会损坏“状态”。

所以,我的问题是:一个推测执行的cpu分支可以包含访问RAM的操作码吗?

我的意思是,访问RAM并不是一项“原子”操作-如果当前不在cpu缓存中,则从存储器中读取一个简单的操作码会导致实际的RAM访问,这可能是非常极端的。从cpu角度来看,这很耗时。

如果在推测分支中确实允许这种访问,那么它仅用于读取操作吗?因为,我只能假定,根据写入操作的大小,如果放弃分支并执行“回滚”操作,则恢复写入操作可能会非常缓慢且棘手。 而且,可以肯定的是,至少在某种程度上支持读/写操作,因为据我所知,某些cpu上的寄存器本身实际上位于cpu缓存上。

因此,也许更精确的表述为:以推测方式执行的代码段的局限性是什么?

解决方法

投机乱序(OoO)执行的基本规则是:

  1. 保留按程序顺序顺序运行的指令的错觉
  2. 请确保推测包含在可以检测到错误推测的情况下可以回滚的事物,并且其他内核无法观察到错误值。物理寄存器,是跟踪指令顺序的后端本身,但不缓存。缓存与其他核心保持一致,因此存储必须在非推测性之后才提交缓存。

OoO执行程序通常通过将一切视为投机直到退休而实现。每个加载或存储都可能出错,每个FP指令都可能引发FP异常。分支是特殊的(与异常相比),只是分支的错误预测并不罕见,因此处理early detection and roll-back for branch misses的特殊机制很有帮助。


是的,由于可缓存的加载没有副作用,因此可以推测性地执行并且OoO。

借助存储缓冲区,存储指令也可以推测性地执行。 存储的实际执行只是将地址和数据写入存储缓冲区。(相关:Size of store buffers on Intel hardware? What exactly is a store buffer?比这具有更多的技术性,并且具有更多的x86焦点。我认为此答案适用于大多数ISA。

提交到L1d缓存的时间是 在ROB退出存储指令后的某个时间,即,当已知存储是非推测性存储时,关联的存储缓冲区条目“毕业生”并变为合格提交缓存并在全球范围内可见。存储缓冲区使执行与其他内核所看不到的一切脱钩,并使该内核与高速缓存未命中的存储区分开来,因此即使在有序CPU上,它也是一个非常有用的功能。

在存储缓冲区条目“毕业生”之前,当回滚错误推测时,可以将其与指向它的ROB条目一起丢弃。

(这就是为什么即使是有序排列的硬件内存模型也仍然允许StoreLoad重新排序https://preshing.com/20120930/weak-vs-strong-memory-models/,这对于获得良好性能几乎不让以后的加载等待实际的存储真正提交至关重要。)

实际上,存储缓冲区是循环缓冲区:由前端分配的条目(在分配/重命名管道阶段期间),并在将存储提交到L1d高速缓存时释放。 (通过MESI与其他内核保持一致)。

可以通过从存储缓冲区到L1d的提交来实现x86等严格排序的内存模型。条目是按程序顺序分配的,因此存储缓冲区基本上可以是硬件中的循环缓冲区。如果存储缓冲区的头部用于尚未准备好的缓存行,则顺序较弱的ISA可能会查看较年轻的条目。

某些ISA(尤其是顺序较弱的ISA)也会合并存储缓冲区条目,以从一对32位存储for example中创建对L1d的单个8字节提交。


假定读取可缓存的内存区域没有副作用,并且可以由OoO执行人员,硬件预取或其他方式进行推测性的操作。错误的推测会通过触摸缓存行而“污染”缓存并浪费一些带宽,而缓存行不会执行真正的路径(甚至可能触发TLB未命中的推测性页面遍历),但这是唯一的缺点 1

必须将

MMIO区域(其中读取 do 具有副作用,例如使网卡或SATA控制器执行某些操作)标记为不可缓存,以便CPU知道来自该物理地址的推测读取是不允许。 If you get this wrong,your system will be unstable-我的回答涵盖了许多您正在询问的有关投机负载的详细信息。

高性能CPU的负载缓冲区具有多个条目,以跟踪运行中的负载,包括L1d缓存中未命中的负载。 (即使在顺序CPU上也允许未命中和未命中,仅当/当一条指令试图读取尚未准备好的加载结果寄存器时,该命令才会停止运行。)

在OoO exec CPU中,当一个装载地址在另一个装载地址之前准备好时,它也允许OoO exec。当数据最终到达时,等待装载结果输入的指令准备就绪(如果它们的其他输入也准备就绪)。因此,必须将负载缓冲区条目连接到调度程序(在某些CPU中称为预留站)。

有关About the RIDL vulnerabilities and the "replaying" of loads的更多信息,请参阅https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)#Mechanism,以了解当数据可能从L2到达L2命中时,Intel CPU如何通过在周期中积极尝试启动它们来专门处理正在等待的uops。


脚注1 :这一缺点,加上用于检测/读取微体系结构状态(高速缓存行或冷线)到架构状态(寄存器值)的定时侧通道,才使Spectre成为可能。 (http://blog.stuffedcow.net/2018/05/meltdown-microarchitecture/

了解崩溃对于了解Intel CPU如何选择处理错误负载的详细信息非常有用。 whatever Intel does for transactional memory


并且可以肯定的是,支持读/写操作

是的,如果您要谈论的是将现代的x86解码为uops指令,则可以通过将它们解码以在逻辑上分开的加载/ ALU /存储操作进行解码。负载的工作方式与正常负载类似,存储将ALU结果放入存储缓冲区。正常情况下,可以通过无序的后端计划所有这三个操作,就像您编写了单独的说明一样。

如果您的意思是 atomic RMW,那么那真的不是推测。缓存是全局可见的(共享请求可以随时出现),并且无法回滚(很好,Can num++ be atomic for 'int num'? ...除外)。您绝对不能在缓存中输入错误的值。有关如何处理原子RMW(尤其是在现代x86上)的方法,请参见Are loads and stores the only instructions that gets reordered?,以通过延迟对加载和存储提交之间的那条线的共享/无效请求的响应来处理。

但是,这并不意味着lock add [rdi],eax会序列化整个管道:load-linked / store-conditional表明 other 独立指令的推测性OoO执行程序可以在原子RMW周围发生。 (与lfence这样的执行屏障会耗尽ROB的情况一样)。

许多RISC ISA仅通过Why is the size of L1 cache smaller than that of the L2 cache in most of the processors?指令提供原子RMW,而不是单个原子RMW指令。

[读/写操作...]至少在一定程度上是由于据我所知,某些CPU上的寄存器本身实际上位于CPU缓存上。

嗯?错误的前提,这种逻辑没有道理。缓存必须始终正确,因为另一个内核可能会随时要求您共享它。与该内核专用的寄存器不同。

寄存器文件是从SRAM之类的SRAM中构建的,但是是独立的。板上有一些带有SRAM 内存(不是高速缓存)的微控制器,并且寄存器使用该空间的早期字节进行内存映射。 (例如AVR)。但是,这些似乎都与乱序执行无关。用于缓存内存的缓存行与用于完全不同的东西(例如保存寄存器值)的绝对不同。

花费晶体管预算来执行推测性执行的高性能CPU根本不可能将高速缓存与寄存器文件结合在一起,这也不是真的合理。然后他们将争夺读/写端口。一个总读取和写入端口总数较大的高速缓存(面积和功耗)比一个微小的快速寄存器文件(许多读取/写入端口)和一个具有几个读取端口和1个写入操作的小型L1d高速缓存(例如32kiB)要贵得多。港口。出于同样的原因,我们使用分离的L1高速缓存,并且具有多级高速缓存,而不是现代CPU中每个内核只有一个大的专用高速缓存。 https://stackoverflow.com/tags/x86/info


相关阅读/背景


  • https://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/-CPU如何处理从存储缓冲区到负载的转发,或者如果存储实际上比此负载年轻(按程序顺序晚),则不处理。
  • https://agner.org/optimize/- x86处理器中的存储到负载转发和内存歧义消除。非常详细的测试结果和商店转发的技术讨论,包括来自与商店的不同部分重叠的狭窄负载以及接近缓存行边界的信息。 (Globally Invisible load instructions在他的microarch PDF中提供了一些关于存储转发是慢速还是快速的信息,但较简单易懂。)
  • {{3}}-从部分与最近的商店部分重叠的负载进行商店转发,但部分情况并没有给我们带来麻烦,这使我们无法了解CPU的工作方式以及它的工作方式/考虑内存(排序)模型没有任何意义。请注意,尽管C ++ 20 std :: atomic_ref可以让您进行与对齐的8字节原子加载重叠的对齐4字节原子存储,但是C ++ std :: atomic无法创建执行此操作的代码。