由于存储负载转发,某些加载指令是否永远不会全局可见?换句话说,如果加载指令从存储缓冲区获取其值,则它永远不必从缓存中读取。
正如通常所说的那样,当从L1D缓存读取时,负载是全局可见的,那些不从L1D读取的负载应使其全局不可见。
答案 0 :(得分:5)
负载的全局可见性概念很棘手,因为负载不会修改内存的全局状态,而其他线程也无法直接观察它。
但是,一旦无序/推测执行后尘埃落定,我们可以告诉线程在某个地方存储负载时的负载值,或基于它的分支。线程的这种可观察行为是重要的。 (或者,如果实验很困难,我们可以使用调试器观察它,和/或只是说明负载可能看到的值。)
至少在像x86这样强烈排序的CPU上,所有CPU都可以就全局可见的商店总数达成一致,更新单个连贯+一致的缓存+内存状态。在x86上,如果不允许StoreStore reordering,则此TSO(总存储顺序)与每个线程的程序顺序一致。 (即,总顺序是来自每个线程的程序顺序的一些交错)。 SPARC TSO也是强烈要求的。
(对于高速缓存旁路存储,全局可见性是指它们从非相干写入组合缓冲区刷新到DRAM中。)
在弱有序的ISA上,线程A和B可能不同意线程C和D完成的存储X和Y的顺序,即使读取线程使用获取负载来确保它们自己的负载不是&#39重新排序。即根本不存在 全球商店订单,更不用说它与程序订单不一样了。
IBM POWER ISA很弱,C ++ 11内存模型(Will two atomic writes to different locations in different threads always be seen in the same order by other threads?)也是如此。这似乎与商店从存储缓冲区提交到L1d缓存时变得全局可见的模型相冲突。但是@BeeOnRope says in comments缓存确实是连贯的,并允许通过障碍恢复顺序一致性。这些多阶效应只发生在SMT(一个物理CPU上的多个逻辑CPU)导致奇怪的本地重新排序。
(一种可能的机制是让其他逻辑线程甚至在它们提交到L1d之前就从存储缓冲区中窥探非推测存储,只保留尚未退役的存储专用于逻辑线程。这可以减少线程间延迟x86不能这样做,因为它会破坏强大的内存模型;当核心上有两个线程处于活动状态时,英特尔的HT会静态分区存储缓冲区。但是当@BeeOnRope发表评论时,一个摘要允许重新排序的模型可能是推理正确性的更好方法。仅仅因为你不能想到硬件机制导致重新排序并不意味着它不会发生。)
如果没有使用障碍或释放存储,那么弱排序的ISA并不像POWER那样在每个核心的本地存储缓冲区中进行重新排序。在许多CPU上,所有商店都有一个全局订单,但它并不是程序顺序的一些交错。 OoO CPU必须跟踪内存顺序,因此单个线程不需要障碍就能按顺序查看自己的存储,但允许存储从存储缓冲区提交到程序顺序以外的L1d肯定会提高吞吐量(特别是如果有的话)多个商店等待同一行,但程序顺序会从每个商店之间的一个关联缓存中驱逐该行。例如一个令人讨厌的直方图访问模式。)
上述内容仍然只是关于商店的可见性,而不是负载。 我们可以解释每个负载在某个时刻从全局内存/缓存中读取的值(忽略任何负载排序规则)吗?
如果是这样,那么所有加载结果都可以通过将所有线程的所有存储和加载组合成一些组合顺序来解释,读取和写入一致的全局内存状态。
事实证明不,我们不能,存储缓冲区打破了这个:部分存储到转载转发给了我们一个反例(例如在x86上)。在存储变为全局可见之前,从存储缓冲区中获取的数据与来自L1d缓存的数据合并。 真正的x86 CPU实际上是这样做的,我们有真正的实验证明它。
如果您只查看完整存储转发,其中加载仅从存储缓冲区中的一个存储获取其数据,您可能会认为存储缓冲区延迟了加载。即负载在使该值全局可见的商店之后立即出现在全局总加载商店订单中。
(这个全局的总加载存储顺序不是尝试创建替代的内存排序模型;它无法描述x86的实际加载顺序规则。)
如果来自另一个核心的商店改变了周围的字节,原子宽负载可以读取一个从未存在过的值,并且在全局一致状态下永远不会 。
请参阅我在Can x86 reorder a narrow store with a wider load that fully contains it?上的回答,以及Alex的答案,以获得可能发生此类重新排序的实验证明,使得该问题中的建议锁定方案无效。 商店然后从同一地址重新加载不是StoreLoad内存障碍。
有些人(e.g. Linus Torvalds) describe this by saying the store buffer isn't coherent。 (Linus正在回复那些独立发明同样无效锁定想法的人。)
另一个Q& A涉及商店缓冲和一致性:How to set bits of a bit vector efficiently in parallel?。您可以执行一些非原子OR来设置位,然后返回并检查由于与其他线程冲突而错过的更新。但是你需要一个StoreLoad屏障(例如x86 lock or
)以确保你在重装时不会看到你自己的商店。
此定义与x86手册一致,该手册表示负载未与其他负载重新排序。即他们从本地核心的内存视图加载(按程序顺序)。
无论其他任何线程是否可以从该地址加载该值,负载本身都可以全局可见。
答案 1 :(得分:2)
我不确定全局可见性是一个有趣的加载操作概念(澄清requested),但是如果你想用它来解决一些语义参数,那么你将不得不依赖于定义。例如,如果您为负载定义全局可见性是从L1缓存加载值的时刻,并且不承认存储转发的可能性,那么答案就是“它从来没有变得可见“或”你的定义是错误的“。
然而,实际上,人们可能会想到从系统中的某个特定商店接收其值。通过这种方式,我们可以说商店的全局可见性(可能是这些商店的部分或全部订单),然后讨论哪些负载可以接收其价值商店。通过这种方式,由各种负载接收的一系列值将它们置于一种全局时间中(尽管如果存储仅部分排序,则可能仅部分排序)。
在此模型中,加载通常从某些全局可见的商店接收其值,但在商店转发的特殊情况下,加载从尚未全局可见的商店接收其值!在实践中,商店(或覆盖它的后继商店)将(a)在某些时候变得全局可见,因为它从商店缓冲区写入L1或(b)由于某些事件而被丢弃,例如推测失败,中断,异常等。在商店被丢弃的情况下,我们不必担心:加载仅按程序顺序从早期商店获取其值,因此当存储被丢弃时,程序顺序中的所有后续指令也将被丢弃,包括负载。
如果关联商店最终变得全局可见,则会产生一种有趣的时间旅行类型效果:本地CPU上的负载可能比其他处理器更早地看到商店,特别是它可能会看到它与系统上的其他商店无关。这种影响是具有存储转发的系统通常具有与之关联的重新排序的一个原因 - 例如,在强x86内存模型上,允许的重新排序正是由存储缓冲和存储转发引起的。
答案 2 :(得分:2)
负载从 RS(保留站)分派并通过 AGU(地址生成单元)到达在分配阶段为相应 ROB(重新排序缓冲区)条目分配的负载缓冲区条目。分配加载缓冲区条目时,它会使用当时最新的 SBID(存储缓冲区 ID)着色。有颜色表示存储缓冲区中最近存储的条目编号(又名 ID)被插入到加载缓冲区条目中。存储缓冲区包括SAB(Store Address Buffer)和SDB(Store Data Buffer);每个商店都有一个条目(因为每个商店都是 2 个 uops,通常是微融合的)并且它们都具有相同的索引(条目号也就是 SBID)。
我认为一旦地址有效,条目中的有效位就会被设置,这意味着它们已准备好发送(并在数据最终写回 ROB 时被清除)。
还有一个推测性内存消歧预测器,它可能涉及有效位的设置,以表明它被预测不会与它着色的 SBID 和尾指针存储之间的任何存储混淆存储缓冲区(SAB 中的存储地址和 SDB 中的数据)。如果它被预测为别名,或者实际上是别名(即它在存储缓冲区中搜索一个地址并使用 SAB 中的位掩码来确定该条目是否可以满足它(位掩码说明字节监督者的特权级别/非supervisor),并使用操作码中的隐含大小来获取存储操作正在存储到的地址范围。如果可以满足,则从 SDB 条目中读取),它进行推测性存储到加载转发使用SDB中的数据,将数据插入到加载缓冲区中,加载在LB(Load Buffer)中完成,但不退出LB。存储到加载转发确保读取永远不会与对同一位置的较旧写入重新排序,因为读取将始终使用存储到加载转发。我认为在对 LFENCE 之后的商店进行预测之前,需要计算 LFENCE 的 SBID 之前的所有商店地址。
如果没有预测到别名,则分派负载(并且总是按照相对于其他负载的严格顺序分派负载,除非负载具有非时间性命中或要到 USWC(Uncacheable Speculative Write Combining memory type) ) 内存(虽然与stores不同,现阶段不知道是否是USWC)。负载并行进入dTLB(数据TLB)/L1d(L1数据缓存)。
在任何时候,当存储地址在 SAB 中完成且任何 SBID 小于或等于(考虑环绕)有问题的负载的彩色 SBID 时,它可以使所做的内存消歧预测无效,并且流水线被刷新,因为管道现在要么使用存储在它应该使用它执行存储到加载转发的存储之前存储的陈旧数据,要么使用来自它实际拥有的存储的错误存储到加载转发数据没有依赖。
当数据加载到指定的物理目标寄存器中时,数据在 ROB 中变为有效。当 ROB 中的数据有效且引退指针指向条目时,加载不再是推测性的并获取高级位。如果设置了指示 SAB 尾指针和彩色 SBID 之间的所有存储都已计算其地址的位,则负载可以从 LB 退出(从 LB 中移除)。除非它是高级加载指令,在这种情况下,它现在可以执行,因为它是高级且已从 ROB 中退出。
LFENCE 被分派到加载缓冲区,并且仅在所有先前的 uops 已从 ROB 退出并且所有先前的加载指令已从 ROB+LB 退出时才执行(发送到 L1d 缓存)(根据它声称具有的指令流序列化属性,它可能会在一个循环中自行退出,而不是在同一循环中在 ROB 之前的 1 或 2 条其他指令中退出)。当 ROB 告诉它们可以退出(不再推测)并且获取的数据有效并且加载不再是内存推测时,加载指令就会退出。 LFENCE 在加载缓冲区和 ROB 的尾部时分派(在所有读取缓冲区全局可见之前它无法退出。我认为这意味着它确保任何高级加载指令(从 ROB 退出后执行的指令以及何时执行)它们被标记为高级),例如 PREFETCH
已分配读取缓冲区。常规加载分配读取缓冲区并读取它们的数据,并且在它们可以退休之前它在加载缓冲区中变得有效。在这种情况下全局可见意味着所有以前的读取LFB(行填充缓冲区)已收到来自环的全局可见通知(which could come before the read response containing the data, or could be packaged into the read response,这可能意味着它必须等待所有读取完成而不是被确认)(当然,指令从 MOB(内存顺序缓冲区)中退出的数据已经返回,它们已经是全局可见的,但是高级加载指令可能尚未分配读取缓冲区或已确认它们是全局可见的)(这类似于 definit全局可见商店的离子,其中响应 RFO(读取所有权),对 LFB 的全局观察可能会出现在通知中,即核心拥有该线路的许可(独占访问)并且其他核心已失效,这将在要写入的行中的实际数据返回到内核之前出现,假设这将始终在响应失去该行许可的窥探之前被写回)。当 LFENCE 分派时,L1d 缓存将其视为 nop 并且它完成,在 ROB 中退休,即从 LB 中删除,并且在加载缓冲区中之前的 uop 被阻止分派到 L1d 缓存现在允许发送。
负载的全局可见性确实会影响其他内核的缓存一致性状态,我认为这就是 LFENCE
要求负载全局可见的原因。内核中的负载未命中进入 LLC(最后一级缓存),它有一个监听过滤器,显示只有一个其他内核拥有该线路。如果 1>= 内核拥有该线路,则需要将该内核降级到 S 状态并使其写回修改后的数据。然后可以将写入 LLC 的数据返回到具有 S 状态和全局可见通知的请求内核。如果核心中的负载未命中而未命中 LLC,则 LLC 可能会在向家乡代理发送请求以从内存中获取它的同时立即发送全局可见的通知(或者如果它是多套接字系统,则 LLC 必须等待确认来自家乡代理,它不需要窥探其他内核,然后才能向内核发送全局可观察通知)。
我认为高级加载是一种不再具有推测性并正在等待数据返回并变得有效的加载,或者它已经有效所以立即退休,而高级加载指令是在它之后调度的指令已从 ROB 退休。
答案 3 :(得分:0)
让我稍微扩展一下这个问题并讨论实现存储加载转发的正确性方面。 (彼得的答案的后半部分直接回答了我的想法)。
存储加载转发会更改负载的延迟,而不是其可见性。除非因为一些错误推测而被冲洗,否则商店最终将成为全球可见的。如果没有存储加载转发,则负载必须等到所有冲突的存储都退出。然后负载可以正常获取数据。
(冲突存储的确切定义取决于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),则存储加载转发将是非法优化。
存储加载转发可能会改变每个全局内存顺序发生的概率,因为它可以减少加载的延迟。