当前位置: 首页 > 知识库问答 >
问题:

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

闾丘谦
2023-03-14

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

//CPU 0|//CPU 1 tem0=x;|tem1=y; y=1;|x=1;

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

我猜我的一个假设一定是错的,类似以下的假设一定成立:

>

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

存储可以在提交之前变得可见。也许某个地方的内存缓冲区被允许将存储转发到加载到不同的线程,即使加载更早入队?

完全是别的东西?

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

共有1个答案

郎俊雅
2023-03-14

你的假设要点在我看来都是正确的,只是你可以构建一个uarch,在这个uarch中,只要检查负载的权限(TLB)以确保它确实可以发生,负载就可以从OoO核心中退出。可能有OoO exec CPU可以做到这一点(更新:显然有)。

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

你是对的,存储在退役前不能对任何其他内核可见。这种方式是疯狂的。即使在SMT内核(一个物理内核上的多个逻辑线程)上,它也会将两个逻辑线程上的猜测链接在一起,如果其中一个检测到错误猜测,它们都需要回滚。这将破坏SMT让一个逻辑线程利用其他逻辑线程的停顿的目的。

(相关:使失效但尚未提交(到L1d)的存储对同一核心上的其他逻辑线程可见,是一些真正的PowerPC实现使得线程可能在存储的全局顺序上存在分歧的原因。对不同线程中不同位置的两个原子写入是否总是以相同的顺序被其他线程看到?)

具有顺序执行的CPU可以启动加载(检查TLB并写入加载缓冲区条目),并且仅当指令在准备就绪之前尝试使用结果时才会暂停。之后的指令(包括存储)可以正常运行。这基本上是为了在有序管道中保持良好的性能所必需的;每次缓存未命中(甚至仅仅是L1d延迟)都会出现延迟,这是不可接受的。即使在顺序CPU上,内存并行也是一件事;它们可以有多个负载缓冲区来跟踪多个未完成的缓存未命中。现代智能手机中仍广泛使用Cortex-A53等高(ish)性能有序ARM内核,而在使用结果寄存器之前调度负载是众所周知的阵列循环重要优化。(展开甚至软件管道。)

因此,如果加载在缓存中未命中,但存储命中(并在较早的缓存未命中加载获取其数据之前提交给L1d),则可以对加载存储进行重新排序。(Jeff Preshing《内存重新排序简介》将该示例用于LoadStore,但根本不涉及uarch的详细信息。)

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

因此,顺序管道上的顺序是:

>

  • <代码>lw r0,[r1] TLB命中,但在L1d缓存中未命中。加载执行单元将地址(r1)写入加载缓冲区。任何稍后尝试读取r0的指令都将暂停,但我们可以确定负载没有出现故障。

    在将r0绑定到等待加载缓冲区就绪的情况下,lw指令本身可以离开管道(失效),以后的指令也可以离开管道。

    任何其他不读r0的指令。这将使有序管道停止。

    sw r2,[r3]存储执行单元将地址数据写入存储缓冲区/队列。然后这条指令可以退出。

    探测负载缓冲区发现此存储区与挂起的负载不重叠,因此它可以提交到L1d。(如果它已经重叠,在MESI RFO完成之前,您无法提交它,而快速重启会将传入数据转发到加载缓冲区。因此,即使不探测每个存储区,处理这种情况也可能不会太复杂,但让我们只看一下单独的缓存线情况,在这种情况下,我们可以对加载存储区进行重新排序)

    致力于L1d=变得全局可见。这可能发生在早期加载仍在等待缓存行到达的时候。

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

    对于具有小OoO窗口的uarches来说,这可能是一个很好的设计理念,该窗口太小,无法隐藏缓存未命中。(公平地说,是每个高性能OoO exec CPU:内存延迟通常太高而无法完全隐藏。)

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

  •  类似资料:
    • 我读过很多关于内存排序的文章,他们都只说CPU会重新排序加载和存储。 CPU(我对x86 CPU特别感兴趣)是否只对加载和存储进行重新排序,而不对其拥有的其余指令进行重新排序?

    • 问题内容: df =DataFrame({‘a’:[1,2,3,4],’b’:[2,4,6,8]}) >>> df[‘x’]=df.a + df.b >>> df[‘y’]=df.a - df.b >>> df a b x y 0 1 2 3 -1 1 2 4 6 -2 2 3 6 9 -3 3 4 8 12 -4 现在,我想重新排列列顺序,按如下方式使“ x”,“ y”列成为第一列和第二列: 但

    • 问题内容: ES版本:1.5(Amazon Elasticsearch) 我的目标:在某个字段上具有重复数据删除功能的搜索结果。我目前正在对聚合进行一些研究,以解决重复数据删除问题。因此,我的结果是一个带有1个大小的存储桶的列表存储桶。但是,我找不到订购存储桶列表的方法。 当前查询: 结果: 我想看到第二个存储桶,其中max_score = 68.78424为第一个。这可能吗? 如果不建议使用聚合

    • 问题内容: 我在伪操作环境中工作,我们在收到数据后制作新图像。有时,当输入新数据时,我们需要重新打开图像并更新该图像以创建合成,添加叠加等。除了添加到图像之外,还需要修改标题,图例等。 matplotlib中有内置的东西可以让我存储和重新加载matplotlib.pyplot对象以供以后使用吗?它需要保持对所有相关对象(包括图形,线条,图例等)的访问。也许咸菜是我想要的,但我对此表示怀疑。 问题答

    • 在HSQLDB中,我试图创建一个存储过程,在更新数据库后执行提交。 类似:创建过程MY_PROC(p_id整数)修改SQL数据开始原子更新提交结束 创建此过程时,通过调用JDBC Statement.execute()方法,我得到了一个错误:SQLSynTaxErrorExctive:意外令牌:COMMIT必需:END 如果没有COMMIT语句,则过程将被正确编译。 知道我做错了什么吗?

    • 问题内容: 我有一张像下面的桌子, 我想使用“名称”列按字母顺序重新排序,并使用此新顺序重置ID(自动递增),以得到以下结果 问题 :如何使用MYSQL执行此操作? 问题答案: 请问您为什么要这么做? 如果有人修改了任何名称值或插入了新行,则会使您的订购方案混乱。试图以表的其他位置(名称列)已经可用的PK顺序存储一些含义似乎是多余的,因此是个坏主意。 更好的解决方案是不用担心ID列的值,而在应用程