关于在 x86 上缓存命中存储之前执行缓存未命中加载的指令排序



给定下面显示的小程序(手工制作,从顺序一致性/TSO的角度来看看起来相同),并假设它由超标量无序x86 CPU运行:

Load A <-- A in main memory
Load B <-- B is in L2
Store C, 123 <-- C is L1

我有几个问题:

  1. 假设指令窗口足够大,这三个指令会同时被获取、解码和执行吗?我认为不会,因为这会破坏程序顺序的执行。
  2. 第二次加载从内存中获取 A 的时间比从内存中获取 B 的时间长。后者是否必须等到第一个完全执行?B 的获取是否仅在加载 A 完全执行后才开始?还是等到什么时候
  3. 为什么商店必须等待装载?如果是,指令是等待在存储缓冲区中提交,直到加载完成还是解码后它必须坐下来等待加载?

谢谢

术语:"指令窗口"通常是指无序执行窗口,CPU可以通过该窗口找到ILP。 即 ROB 或 RS 大小。 请参阅了解 lfence 对具有两个长依赖链的循环的影响,以增加长度

在单个周期中可以通过管道的指令数的术语是管道宽度。 例如,Skylake是4宽的超标量无序。 (其管道的一部分,如解码、uop-cache fetch 和 retirement,比 4 uops 宽,但问题/重命名是最窄的一点。

>术语:">等待在存储缓冲区中提交" 存储数据 + 地址在存储执行时写入存储缓冲区。 它在停用后的任何时间点从存储缓冲区提交到 L1d,当已知它是非推测性的时。

(按程序顺序,以维护无存储重新排序的 TSO 内存模型。存储缓冲区允许存储在此内核内无序执行,但仍按顺序提交到 L1d(并变得全局可见)。执行存储 = 将地址 + 数据写入存储缓冲区。
推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?
还有什么是存储缓冲区?和
英特尔硬件上存储缓冲区的大小?究竟什么是存储缓冲区?


前端无关紧要。 3 条连续的指令很可能在同一个 16 字节的提取块中获取,并且可能会在与组相同的周期内进行预解码和解码。 并且(也或相反)作为一组 3 或 4 个 uops 的一部分发布到无序后端。 IDK 为什么您认为其中任何一个都会导致任何潜在的问题。

前端(从获取到发出/重命名)按程序顺序处理指令。 同时处理不会将较晚的指令放在较早的指令之前,而是将它们放在同时。 更重要的是,它保留了程序顺序的信息;这不会丢失或丢弃,因为它对于依赖于前一个1的指令很重要!

大多数流水线阶段之间都有队列,因此(例如在英特尔 Sandybridge 上)作为一组最多 6 条指令的一部分进行预解码的指令可能不会作为同一组最多 4 条指令(或更多宏融合)的一部分命中解码器。 请参阅 https://www.realworldtech.com/sandy-bridge/3/以获取,下一页进行解码。 (和 uop 缓存。


执行(将 uops 从乱序调度程序调度到执行端口)是排序很重要的地方。乱序调度程序必须避免破坏单线程代码。阿拉伯数字

通常问题/重命名远远领先于执行,除非您在前端遇到瓶颈。 因此,通常没有理由期望一起发布的 uop 将一起执行。 (为了便于讨论,让我们假设您显示的 2 个加载确实在同一周期内被调度执行,无论它们如何通过前端到达那里。

但无论如何,这里同时启动加载和存储都没有问题。 uop 调度程序不知道负载是否会在 L1d 中命中或未命中。 它只在一个周期内向加载执行单元发送 2 个加载 uop,并向这些端口发送一个存储地址 + 存储数据 uop。

  1. [加载排序]

这是棘手的部分。

正如我在回答+评论您的最后一个问题中所解释的那样,现代x86 CPU将推测性地使用来自负载B的L2命中结果作为后续指令,即使内存模型要求此负载发生在负载A之后。

但是,如果在加载 A 完成之前没有其他内核写入缓存行 B,则没有什么可以区分。内存顺序缓冲区负责检测在早期加载完成之前加载的缓存行的失效,并在允许加载重新排序可能会更改结果的极少数情况下执行内存顺序错误推测管道刷新(回滚到停用状态)。

  1. 为什么商店必须等待加载?

它不会,除非存储地址取决于负载值。uop 调度程序将在输入准备就绪时将存储地址和存储数据 uop 调度到执行单元。

它位于按程序顺序加载之后,就全局内存顺序而言,存储缓冲区将使它在加载之后更远。在存储停用之前,存储缓冲区不会将存储数据提交到 L1d(使其全局可见)。 由于是在负载之后,他们也会退休。

(退休是为了允许精确的异常,并确保以前的指令没有例外或错误预测的分支。 按顺序退休允许我们确定指令在退休后是非投机性的。

所以是的,这种机制确实确保存储在两个加载都从内存中获取数据之前无法提交到 L1d(通过 L1d 缓存为所有内核提供一致的内存视图)。 因此,这可以防止 LoadStore 重新排序(使用后来的存储对早期加载)。

我不确定是否有任何弱排序的 OoO CPU 进行 LoadStore 重新排序。 在按顺序 CPU 上,当缓存未命中负载出现在缓存命中存储之前,CPU 使用记分板来避免在实际从寄存器读取加载数据之前停止(如果仍未准备就绪)。 (LoadStore 是一个奇怪的版本:另请参阅 Jeff Preshing的内存屏障就像源代码控制操作)。 也许一些 OoO exec CPU 也可以在已知肯定会发生时跟踪停用后的缓存未命中存储,但数据尚未到达。 x86 不会这样做,因为它会违反 TSO 内存模型。


脚注1:在某些架构(通常是VLIW)中,同步指令束以软件可见的方式成为架构的一部分。 因此,如果软件不能用可以同时执行的指令填充所有 3 个插槽,它必须用 NOP 填充它们。 它甚至可以被允许将2个寄存器与包含mov r0, r1mov r1, r0的捆绑包交换,这取决于ISA是否允许同一分发包中的指令读取和写入相同的寄存器。

但 x86 不是这样的:超标量无序执行必须始终保持按程序顺序一次运行一条指令的错觉。OoO exec 的基本规则是:不要破坏单线程代码。

任何违反此规定的事情都只能通过检查危险来完成,或者在检测到错误时

推测性地回滚。 脚注2:(接脚注1)

您可以获取/解码/发出两个背靠背inc eax指令,但它们不能在同一周期内执行,因为寄存器重命名 + OoO 调度程序必须检测到第二个指令读取第一个指令的输出。

最新更新