如何通过按顺序提交load-> store重新排序?

Pos*_*pia 6 arm cpu-architecture memory-barriers

ARM允许重新排序加载后续存储,以便以下伪代码:

// CPU 0 | // CPU 1 temp0 = x; | temp1 = y; y = 1; | x = 1;

可以导致temp0 == temp1 == 1(并且,这在实践中也是可观察到的).我无法理解这是怎么发生的; 似乎有序提交会阻止它(这是我的理解,它存在于几乎所有的OOO处理器中).我的理由是"在提交之前,负载必须具有其值,它在存储之前提交,并且在提交之前,存储的值不会对其他处理器可见."

我猜我的一个假设肯定是错的,并且必须遵循下列之一:

  • 说明不需要提交一路有序.稍后的存储可以安全地提交并在之前的加载之前变得可见,只要在存储提交核心时可以保证先前的加载(以及所有中间指令)不会触发异常,并且加载的地址是保证与商店不同.

  • 负载可以在其值已知之前提交.我不知道如何实现这一点.

  • 商店在提交之前可以显示.也许某个内存缓冲区允许将存储转发到另一个线程的加载,即使负载先前已加入?

  • 还有别的吗?

有许多假设的微体系结构特征可以解释这种行为,但我最好的是那些实际存在于现代弱有序CPU中的那些.

Pet*_*des 7

您的假设所有内容对我来说都是正确的,除了您可以构建一个uarch,其中只需检查负载上的权限(TLB)以确保它肯定会发生,负载可以从OoO核心退出.可能有OoO exec CPU执行此操作(更新:显然有).

我认为x86 CPU需要负载来实际让数据在退出之前到达,但是他们强大的内存模型无论如何都不允许LoadStore重新排序.所以ARM肯定会有所不同.

你是对的,在退休之前商店不能被任何其他核心看到.那种方式就是疯狂.即使在SMT核心(一个物理核心上的多个逻辑线程)上,它也会将两个逻辑线程的推测链接在一起,如果任何一个检测到错误推测,则要求它们都回滚.这将破坏SMT的目的,让一个逻辑线程利用其他人的档位.

(相关:对同一核心上的其他逻辑线程可见的已退役但尚未提交(到L1d)的存储是一些真正的PowerPC实现如何使线程在全局存储顺序上不同意. 将两个原子写入不同位置在不同的线程中,其他线程总是以相同的顺序看到它们吗?)


具有按顺序执行的CPU可以启动加载(检查TLB并写入加载缓冲区条目),并且只有在指令尝试在结果准备好之前使用结果时才会停止.然后,包括商店在内的后续指令可以正常运行.这对于有序流水线中的非可怕性能基本上是必需的; 停止每次高速缓存未命中(或甚至只是L1d延迟)将是不可接受的.内存并行性甚至在有序CPU上也是如此; 它们可以有多个加载缓冲区来跟踪多个未完成的缓存未命中.像Cortex-A53这样高性能的有序ARM内核仍然广泛应用于现代智能手机中.

因此,如果加载在高速缓存中未命中但存储命中(并且在较早的高速缓存未命中负载获取其数据之前提交到L1d),则可以获得LoadStore重新排序.(Jeff Preshing对内存修改的介绍使用了LoadStore的这个例子,但根本没有进入uarch细节.)

在检查TLB和/或任何内存区域内容后,加载不会出错.该部分必须在退休之前完成,或者在它到达有序管道的末尾之前完成.就像坐在商店缓冲区中等待提交的退休商店一样,在某个时刻肯定会发生一个位于加载缓冲区中的退役负载.

所以有序流水线的顺序是:

  • lw r0, [r1] TLB命中,但在L1d缓存中未命中.加载执行单元将address(r1)写入加载缓冲区.任何后来尝试读取的指令都r0将停止,但我们确信负载没有故障.

    随着r0绑在等待的负荷缓冲区是准备好的,lw指令本身可以离开管道(退休),所以以后可以说明.

  • 任何数量的其他指令都没有读取r0.这将阻止有序管道.
  • sw r2, [r3]存储执行单元将地址+数据写入存储缓冲区/队列.然后该指令可以退休.

    探测加载缓冲区会发现此存储不与挂起的加载重叠,因此它可以提交到L1d. (如果它已经重叠,你无法提交它,直到MESI RFO完成,并且快速重启会将输入的数据转发到加载缓冲区.因此,如果不对每个商店进行探测,处理这种情况可能不会太复杂,但是,我们只看一下我们可以获得LoadStore重新排序的单独缓存行情况)

    致力于L1d =变得全球可见.当早期加载仍在等待缓存行到达时,可能会发生这种情况.


对于OoO CPU,您需要一些方法将负载完成重新绑定到OoO核心,以获取等待加载结果的指令.我想这是可能的,但这意味着寄存器的架构/报废值可能不存储在核心的任何地方.由错误推测引起的流水线冲洗和其他回滚将不得不依赖于传入负载与物理和架构寄存器之间的关联.(不要在管道回滚上刷新存储缓冲区已经是CPU必须要做的事情了.停留但尚未提交的存储缓冲区存储无法回滚.)

对于具有小OoO窗口的搜索来说,这可能是一个很好的设计理念,这个窗口太小而无法隐藏缓存未命中.


我们在OoO ARM上有LoadStore重新排序的实验证据:https ://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf的7.1节显示了"load buffering"的非零计数Tegra 2,基于无序的Cortex-A9 uarch.我没有查看所有其他内容,但我确实重写了答案,建议这也是无序CPU的可能机制.但我不确定是否是这种情况.

  • @BeeOnRope:在检查了 TLB 和/或任何内存区域内容后,负载就不会出错。该部分必须在退出之前或到达有序管道的末端之前完成。就像存储缓冲区中等待提交的退役存储一样,加载缓冲区中的退役加载肯定会在某个时刻发生。 (2认同)
  • @zanmato:x86(至少是英特尔,大概是AMD)确实进行*推测性* LoadLoad 重新排序,并在退役时确认(?)缓存行尚未失效,因此可以假装加载在架构上现在发生并得到相同的值。禁止 LoadLoad 重新排序是读取共享数据有时会导致“machine_clears.memory_ordering”事件的原因之一。请参阅[生产者-消费者在超级同级与非超级同级之间共享内存位置的延迟和吞吐量成本是多少?](/sf/ask/3192188961/) (2认同)