ARM允许使用后续存储对加载进行重新排序,以便以下伪代码:
// CPU 0 | // CPU 1
temp0 = x; | temp1 = y;
y = 1; | x = 1;
可能会导致temp0 == temp1 == 1
(在实践中也是可以观察到的)。我无法理解这种情况如何发生;似乎按顺序提交会阻止它(据我的理解,这几乎在所有OOO处理器中都存在)。我的推理是:“负载在提交之前必须具有其值,在存储之前必须具有其值,并且存储的值直到其提交才对其他处理器可见。”
我猜我的一个假设一定是错误的,并且必须满足以下条件之一:
指令不需要一路提交 。以后的存储区可以安全地提交并在较早的加载之前可见,只要在存储区提交时核心可以保证先前的加载(以及所有中间指令)不会触发异常,并且加载的地址为保证与商店不同。
负载可以在知道其值之前提交。我不知道如何实现。
存储在提交之前可以变得可见。也许允许某个地方的内存缓冲区将存储转发到其他线程的加载,即使加载已在较早之前入队?
还有其他事情吗?
有许多假设的微体系结构特征可以解释这种行为,但是我对现代弱顺序CPU中实际存在的特征感到最好奇。
答案 0 :(得分:6)
您的假设要点对我来说都是正确的,除了您可以构建一个uarch,在其中检查负载(TLB)以确保确实可以发生之后,就可以从OoO核心中退出负载。可能有OoO exec CPU可以做到这一点(更新:显然有)。
我认为x86 CPU要求负载才能使数据真正退休后才能到达,但是它们强大的内存模型始终不允许LoadStore重新排序。因此ARM肯定会有所不同。
您说对了,退休之前其他任何核心都无法看到商店。那就是疯狂。即使在SMT core(一个物理内核上有多个逻辑线程)上,它也会将两个逻辑线程上的推测链接在一起,如果任何一个检测到错误推测,它们都必须回滚。这将破坏SMT的目的,即让一个逻辑线程利用其他线程的停滞状态。
(相关:使已退休但尚未提交给L1d的存储对于同一内核上的其他逻辑线程可见,这是某些真正的PowerPC实现如何使线程有可能在全局存储顺序上产生分歧的原因。{{3} })
按顺序执行的CPU可以启动加载(检查TLB并写入加载缓冲区条目),并且只有在指令试图在准备就绪之前使用该结果时才会暂停。然后,包括商店在内的后续说明即可正常运行。基本上,这是有序管道中非糟糕性能所必需的;在每个高速缓存未命中(甚至只是L1d延迟)时停滞将是不可接受的。内存并行性即使在有序CPU上也是如此。它们可以具有多个加载缓冲区,以跟踪多个未决的高速缓存未命中。像Will two atomic writes to different locations in different threads always be seen in the same order by other threads?这样的高性能有序ARM内核仍广泛用于现代智能手机中。
因此,如果缓存中的负载未命中,但是存储命中了(并在较早的缓存未命中负载获取其数据之前提交了L1d),则可以对LoadStore重新排序。 (Cortex-A53将该示例用于LoadStore,但根本不涉及uarch详细信息。)
检查了TLB和/或任何内存区域内容后,加载不会出错。该部分必须在退出之前或到达有序管道的末端之前完成。就像坐在存储缓冲区中等待提交的已退休存储一样,坐在加载缓冲区中的已退休加载肯定在某个时刻发生。
所以顺序管道上的顺序是:
lw r0, [r1]
TLB命中,但未命中L1d缓存。加载执行单元将地址(r1
)写入加载缓冲区。以后任何尝试读取r0
的指令都将停止,但是我们可以肯定地知道负载没有故障。
r0
与等待该装载缓冲区就绪相关联,lw
指令本身可以离开流水线(退出),以后的指令也可以离开。
sw r2, [r3]
存储执行单元将地址+数据写入存储缓冲区/队列。然后该指令可以退役。
探测加载缓冲区会发现此存储与挂起的加载不重叠,因此可以提交到L1d。(如果已经重叠,则您无法直到将其提交给MESI RFO为止,然后快速重新启动会将传入的数据转发到加载缓冲区,因此即使不对每个存储进行探测,处理这种情况也不会太复杂,而让我们只看一下单独的缓存在线情况下,我们可以对LoadStore重新排序)
致力于L1d =成为全局可见的。当较早的负载仍在等待缓存行到达时,可能会发生这种情况。
对于OoO CPU,您需要某种方法将加载完成绑定回OoO内核,以获取等待加载结果的指令。我想这是可能的,但这意味着寄存器的架构/退休值可能不会存储在内核的任何位置。由于错误推测而引起的管道刷新和其他回滚将不得不依赖于传入负载与物理和体系结构寄存器之间的关联。 (不过,在管道回滚中不刷新存储缓冲区已经是CPU要做的事情。位于存储缓冲区中的已退休但尚未提交的存储区无法回滚。)
对于具有很小的OoO窗口的Uarches来说,这可能是一个不错的设计思路,而OoO窗口又很小,以至于几乎无法隐藏缓存未命中。
我们有实验证据证明在OoO ARM上进行LoadStore重排序:Jeff Preshing intro to memory reording的7.1节显示了https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf上“负载缓冲”的非零计数,该计数基于乱序Tegra 2。我没有查找其他所有对象,但是我确实重写了答案,以表明这也是乱序CPU的可能机制。不过,我不确定情况是否如此。