提问者:小点点

全局不可见加载指令


由于存储负载转发,某些加载指令是否永远不会全局可见?换句话说,如果加载指令从存储缓冲区获取其值,则它永远不必从缓存中读取。
由于通常规定负载在从L1D缓存读取时是全局可见的,因此未从L1D读取的应该使其全局不可见。


共3个答案

匿名用户

负载全局可见性的概念很棘手,因为负载不会修改内存的全局状态,其他线程无法直接观察到它。

但是,一旦无序/推测执行后尘埃落定,如果线程将负载存储在某个地方,或者基于它进行分支,我们就可以知道负载得到了什么值。线程的这种可观察行为很重要。(或者我们可以用调试器观察它,和/或仅仅是推理负载可能看到的值,如果实验很困难的话。)

至少在像x86这样的强有序CPU上,所有CPU都可以就全局可见的存储的总顺序达成一致,从而更新单个连贯一致的缓存状态。在x86上,不允许StoreStore重新排序,这个TSO(总存储顺序)与每个线程的程序顺序一致。(即总顺序是每个线程的程序顺序的一些交错)。SPARCTSO也是这个强有序的。

(正确观察你自己的存储相对于其他存储的全局顺序需要mgrid或类似的:否则存储转发意味着你可以立即看到你自己的存储,在它们对其他核心可见之前。x86TSO基本上是程序顺序加上存储转发。)

(对于缓存绕过存储,全局可见性是当它们从私有写入组合缓冲区刷新到DRAM时。英特尔行填充缓冲区或任何等效的私有写入组合机制,其中存储数据对其他CPU仍然不可见,实际上是存储缓冲区的一部分,用于我们的重新排序目的。)

在弱有序ISA,线程A和B可能不同意由线程C和D完成的存储X和Y的顺序,即使读取线程使用获取加载来确保它们自己的加载没有重新排序。

POWERISAIBM是那么弱,C 11内存模型也是如此(两个原子写入不同线程中的不同位置总是被其他线程以相同的顺序看到吗?)。但POWER上的实践机制是,(退休又名分级)存储在通过提交L1d缓存成为全局可见之前,对其他一些内核变得可见。缓存本身即使在POWER系统中也是一致的,就像所有普通的CPU一样,并且允许通过屏障恢复顺序一致性。这些多阶效应只发生在SMT(一个物理CPU上的多个逻辑CPU)提供了一种无需通过缓存即可查看其他逻辑内核存储的方法。

(一种可能的机制是让其他逻辑线程甚至在提交到L1d之前就从存储缓冲区中窥探非推测性存储,只将尚未停用的存储保留为逻辑线程私有。这可以略微减少线程间延迟。x86不能做到这一点,因为它会破坏强内存模型;当一个内核上有两个线程处于活动状态时,英特尔的HT会静态分区存储缓冲区。但是正如@BeeOnRope评论的那样,允许什么重新排序的抽象模型可能是推理正确性的更好方法。仅仅因为你想不出一个硬件机制来导致重新排序并不意味着它不会发生。)

但是,如果不使用屏障或释放存储,不如POWER弱的弱有序ISA(在实践和/或纸上)仍然会在每个内核的本地存储缓冲区中进行重新排序。在许多CPU上,所有存储都有一个全局顺序,但它不是程序顺序的一些交错。OoO CPU必须跟踪内存顺序,因此单个线程不需要障碍来按顺序查看自己的存储,但是允许存储从存储缓冲区提交到L1d,而不是按照程序顺序,这肯定会提高吞吐量(特别是如果同一行有多个存储挂起,但是程序顺序会从每个存储之间的集合关联缓存中驱逐该行。)

上面仍然只是关于存储可见性,而不是加载。我们可以解释每个加载看到的值在某个时候从全局内存/缓存中读取(不考虑任何加载排序规则)吗?

如果是这样,那么所有的加载结果都可以通过将所有线程的所有存储和加载按某种组合顺序来解释,读写一个连贯的全局内存状态。

事实证明,不,我们不能,存储缓冲区打破了这一点:部分存储到加载转发给了我们一个反例(例如在x86上)。窄存储后跟宽加载可以合并存储缓冲区中的数据和存储变得全局可见之前的L1d缓存中的数据。真正的x86 CPU实际上做到了这一点,我们有真正的实验来证明这一点。

如果你只看完整的存储转发,其中加载只从存储缓冲区中的一个存储获取数据,你可以认为加载被存储缓冲区延迟了。也就是说,负载出现在使该值全局可见的存储之后的全局总加载-存储顺序中。

(这个全局总加载存储顺序不是试图创建替代内存排序模型;它无法描述x86的实际加载排序规则。)

如果来自另一个核心的存储更改了周围的字节,则原子范围的负载可能会读取一个在全局相干状态中从未存在且永远不会存在的值。

请参阅我关于x86可以重新排序具有更宽负载且完全包含它的窄存储吗?的答案,以及Alex的实验证明这种重新排序可能发生的答案,这使得该问题中提出的锁定方案无效。一个存储,然后从同一地址重新加载不是StoreLoad内存屏障。

有些人(例如Linus Torvalds)通过说存储缓冲区不连贯来描述这一点。(Linus是在回答另一个独立发明了相同无效锁定想法的人。)

另一个Q

这个定义与x86手册一致,它说加载不会与其他加载一起重新排序。即它们从本地内核的内存视图加载(按程序顺序)。

加载本身可以变得全局可见,而与任何其他线程是否可以从该地址加载该值无关。

尽管完全不谈论可缓存负载的“全局可见性”可能更有意义,因为它们是从某个地方提取数据,而不是做任何具有可见效果的事情。只有不可缓存的负载(例如来自MMIO区域)才应该被视为可见的副作用。

(在x86上,不可缓存的存储和加载是非常强有序的,所以我认为存储转发到不可缓存的存储是不可能的。除非存储是通过UC加载访问的同一物理页面的WB映射完成的。)

匿名用户

让我稍微扩展一下问题,讨论一下实现store-load转发的正确性方面。(Peter回答的后半部分直接回答了我认为的问题)。

存储负载转发改变的是负载的延迟,而不是它的可见性。除非由于一些错误的猜测而被刷新,否则存储最终还是会变得全局可见。如果没有存储负载转发,负载必须等到所有冲突的存储都退役。然后负载才能正常获取数据。

(冲突存储的确切定义取决于ISA的内存排序模型。在x86中,假设允许存储负载转发的WB内存类型,任何程序顺序较早且目标物理内存位置与负载重叠的存储都是冲突存储)。

尽管如果系统中存在来自另一个代理的任何并发冲突存储,这实际上可能会改变加载的值,因为外部存储可能在本地存储之后但在本地加载之前生效。通常,存储缓冲区不在相干域中,因此存储负载转发可能会降低发生此类事情的概率。这取决于存储负载转发实现的限制;通常不能保证任何特定的加载和存储操作都会发生转发。

存储加载转发还可能导致全局内存顺序,如果没有它,这些顺序是不可能的。例如,在x86的强模型中,允许存储加载重新排序,并且与存储加载转发一起,可以允许系统中的每个代理以不同的顺序查看所有内存操作。

一般来说,考虑一个恰好有两个代理的共享内存系统。让S1(A, B)是具有存储负载转发的序列A和B的可能全局内存订单集,让S2(A,B)是没有存储负载转发的序列A和B的可能全局内存订单集。S1(A,B)和S2(A,B)都是所有合法全局内存订单集S3(A,B)的子集。存储负载转发可以使S1(A,B)不是S2(A,B)的子集。这意味着如果S2(A,B)=S3(A,B),那么存储负载转发将是一个非法的优化。

存储负载转发可能会改变每个全局内存顺序发生的概率,因为它减少了负载的延迟。

匿名用户

负载从RS(Reservation Station)分派并通过AGU(地址生成单元)到达在分配阶段为相应ROB(Reorder Buffer)条目分配的负载缓冲区条目。当分配负载缓冲区条目时,它用当时最近的SBID(存储缓冲区ID)着色。着色意味着存储缓冲区中最近存储的条目号(又名ID)被插入到负载缓冲区条目中。存储缓冲区包括SAB(存储地址缓冲区)和SDB(存储数据缓冲区);每个存储在两者中都有一个条目(因为每个存储是2 uops,通常是微融合的)并且它们都具有相同的索引(条目编号aka. SBID)。

我认为一旦地址有效,条目中的有效位就会被设置,这意味着它们准备好分派(并且在数据最终写回ROB时被清除)。

如果没有预测到别名,则分派负载(并且负载始终以相对于其他负载的严格顺序分派,除非负载具有非时间命中或到USWC(不可缓存推测写入组合内存类型)内存(尽管与存储不同,它在这个阶段不知道它是否是USWC)。负载并行进入dTLB(数据TLB)/L1d(L1数据高速缓存)。

在任何时候,当存储地址在SAB中完成,并且任何SBID小于或等于(考虑到环绕)所讨论的负载的彩色SBID时,它可能会使所做的内存消除歧义预测无效,并且管道被刷新,因为管道现在要么使用存储在它应该执行存储到加载转发的存储之前的陈旧数据,要么使用来自它实际上不依赖的存储的错误存储到加载转发数据。

当数据加载到指定的物理目标寄存器中时,数据在ROB中变得有效。当ROB中的数据有效并且引退指针指向条目时,负载不再是推测性的,并获取高级位。如果设置了一个位,指示SAB尾指针和彩色SBID之间的所有存储都已计算其地址,则负载可以从LB引退(被移除)。除非它是高级加载指令,在这种情况下,它现在可以执行,因为它是高级的,并且已经从ROB引退。

负载的全局可见性确实会影响其他内核的高速缓存一致性状态,我认为这就是为什么LFENCE要求负载是全局可见的。内核中的负载未命中会到达LLC(最后级别缓存),它有一个窥探过滤器,显示只有一个其他内核拥有该行。如果1

我认为高级加载是一种不再推测的加载,它正在等待数据返回并变得有效,或者它已经有效,因此立即退出,而高级加载指令是在从ROB退出后调度的指令。