如何通过按顺序提交实现加载>存储重新排序?



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

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

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

我猜我的一个假设肯定是错误的,下面的一个一定成立:

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

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

  • 商店可以在提交之前变得可见。也许某个地方的内存缓冲区被允许将存储转发到另一个线程,即使加载已经提前排队?

  • 完全是别的吗?

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

您的假设要点在我看来都是正确的,只是您可以构建一个uarch,在该uarch中,只需检查负载的权限(TLB)以确保它肯定会发生,负载就可以从OoO核心中退出。可能有OoO执行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缓存中未命中。加载执行单元将地址(r1)写入到加载缓冲器中。任何稍后尝试读取r0的指令都将暂停,但我们确信加载没有出错。

    由于r0绑定到等待加载缓冲区准备就绪,lw指令本身可以离开管道(引退),以后的指令也可以。

  • 不读取r0的任何数量的其他指令。这将使一个有序的管道停滞。

  • CCD_ 8存储执行单元向存储缓冲器/队列写入地址+数据。然后这个指令就可以退出了。

    探测加载缓冲区会发现此存储与挂起的加载没有重叠,因此它可以提交到L1d(如果重叠,那么无论如何都要等到MESI RFO完成后才能提交,并且快速重启会将传入的数据转发到加载缓冲区。因此,在不探查每个存储的情况下处理这种情况可能不会太复杂,但让我们只看单独的缓存行情况,在这种情况下我们可以获得LoadStore的重新排序)

    提交到L1d=变得全局可见。当早期加载仍在等待缓存行到达时,可能会发生这种情况。


对于OoO CPU,您需要某种方法将加载完成绑定回OoO核心,以便等待加载结果的指令。我想这是可能的,但这意味着寄存器的体系结构/退休值可能不会存储在核心中的任何位置。错误推测的管道刷新和其他回滚必须保留传入负载与物理和体系结构寄存器之间的关联。(不过,在管道回滚时不刷新存储缓冲区已经是CPU必须做的事情。存储缓冲区中已失效但尚未提交的存储无法回滚。)

对于具有小OoO窗口的uarche来说,这可能是一个很好的设计想法,因为OoO窗口太小,几乎无法隐藏缓存未命中。(公平地说,每个高性能OoO exec CPU都是这样:内存延迟通常太高,无法完全隐藏。)


我们有在OoO ARM上LoadStore重新排序的实验证据:第7.1节https://www.cl.cam.ac.uk/~pes20/ppc Supplement/test7.pdf显示";"负载缓冲";基于无序Cortex-A9 uarch的Tegra 2。我没有查找所有其他的,但我确实重写了答案,表明这也是CPU出现故障的可能机制。不过,我不确定是不是这样。

相关内容

最新更新