由于存储加载转发,一些负载指令是否永远不会全局可见?换句话说,如果加载指令从存储缓冲区获取其值,则它永远不必从高速缓存中读取。
通常情况下,当一个加载指令从L1D缓存中读取时,它是全局可见的。而那些不从L1D中读取的指令应该使其在全局范围内不可见。
由于存储加载转发,一些负载指令是否永远不会全局可见?换句话说,如果加载指令从存储缓冲区获取其值,则它永远不必从高速缓存中读取。
通常情况下,当一个加载指令从L1D缓存中读取时,它是全局可见的。而那些不从L1D中读取的指令应该使其在全局范围内不可见。
全局可见性对于负载来说是棘手的概念,因为负载不会修改内存的全局状态,并且其他线程无法直接观察到它。
但是一旦乱序/推测执行后尘埃落定,如果线程将其存储在某个地方或者根据其进行分支,则可以确定负载获取了什么值。线程的这种可观察行为是重要的。(或者我们可以通过调试器观察它,并/或者只是推断出负载可能看到的值,如果一个实验很困难的话)。
mfence
或类似机制:否则,存储转发意味着您可以立即看到自己的存储,而它们还没有对其他核心可见。x86 TSO基本上是程序顺序加上存储转发。)以上仅涉及存储可见性,而非负载。 我们能否解释每个负载看到的值是在某个时刻从全局内存/缓存中读取的(不考虑任何负载顺序规则)?
如果可以,那么所有负载结果都可以通过将所有线程的所有存储和负载放入某个组合顺序来解释,读取和写入一个一致的全局内存状态。
事实证明,不行,存储缓冲区会破坏这一点:部分存储器到负载器的转发为我们提供了一个反例(例如,在x86上)。 窄存储器后跟宽负载器可以将存储器缓冲区的数据与在存储器变为全局可见之前从L1d缓存中获取的数据合并。 真正的x86 CPU实际上就是这样做的,我们有真实的实验来证明它。
如果仅考虑完整的存储器转发,即负载仅从存储器缓冲区中的一个存储器中获取其数据,则可以认为负载被存储器缓冲区延迟了。 也就是说,负载出现在使该值全局可见的存储器之后的全局总负载存储顺序中。
(这个全局总负载存储顺序并不是创建替代内存排序模型的尝试;它没有办法描述x86的实际负载排序规则。)
如果来自另一个核心的存储更改了周围的字节,原子宽度加载可能会读取一个在全局一致状态中从未存在过且永远不会存在的值。
请参阅我的答案Can x86 reorder a narrow store with a wider load that fully contains it?以及Alex的答案,证明了这种重新排序是可以发生的,使得该问题中提出的锁定方案无效。 从同一地址进行存储然后重新加载不是StoreLoad内存屏障。
有些人(例如Linus Torvalds)将其描述为存储缓冲区不一致。(Linus回复的是另一个独立发明了相同无效锁定思想的人。)
涉及存储缓冲区和一致性的另一个Q&A:How to set bits of a bit vector efficiently in parallel?。您可以执行一些非原子OR以设置位,然后返回并检查由于与其他线程的冲突而导致的遗漏更新。但是,您需要一个StoreLoad屏障(例如x86 lock or
),以确保重新加载时不仅看到自己的存储。
该定义与x86手册一致,手册指出加载不会与其他加载重排序。即它们按程序顺序从本地核的内存视图中加载。
负载本身可以独立于任何其他线程是否可以从该地址加载该值而变得全局可见。
尽管也许根本不讨论可缓存加载的“全局可见性”更有意义,因为它们正在从某个地方拉数据,没有任何可见效果。只有不可缓存的负载(例如从MMIO区域)应被认为是可见的副作用。
(在x86上,不可缓存的存储和加载非常强制排序,因此我认为无法将存储转发到不可缓存存储。除非也许通过与UC加载访问相同物理页面的WB映射执行了存储。)
8.2.3.5
或x86-TSO中的示例n6
。这两个示例都是由存储器到加载器转发引起的。这是一种重新排序,在大多数设计中不会发生,但如果没有SLF,则相同但没有SLF的设计将直接矛盾于加粗部分。抛开这些,也许您应该使您的加粗部分更加精确:如果您的意思是“存在一个没有SLF的理论处理器设计,它可以展示与具有SLF的设计上存在的相同的重新排序”,那么当然 - 任何事情都有可能! - BeeOnRope任何时候,当存储地址在SAB中完成,并且其SBID小于或等于问题负载的彩色SBID(考虑到环绕),它可以使内存消歧预测失效,并且流水线被清空,因为流水线现在要么使用存储之前存储的陈旧数据进行存储到负载转发,要么使用来自实际上没有依赖关系的存储的错误存储到负载转发数据。
PREFETCH )都分配了读取缓冲区。常规加载分配读取缓冲区并读取它们的数据,在可以退役之前,它们在加载缓冲区中变为有效。在这种情况下,“全局可见”意味着所有先前的读取LFB(Line Fill Buffers)已经从环中接收到了线的全局通知(可能在包含数据的读取响应中之前到来,也可能打包到读取响应中,这可能意味着它必须等待所有读取完成而不是被确认)。当然,已经从MOB(Memory Order Buffer)退役的指令已经是全局可见的,因为它们的数据已经返回,但是高级加载指令可能尚未分配读取缓冲区或已经被确认为全局可见。这类似于全局可见存储的定义,在响应RFO(Read For Ownership)时,LFB的全局观察很可能是核心拥有该行的许可(独占访问),其他核心已被无效化,这将在实际写入该行的数据之前返回给核心,假设这将始终在响应失去该行权限的窥视之前写回)。当LFENCE派发时,L1d缓存将其视为nop,它完成,从LB中删除并变为senior,而在加载缓冲区中它之前被阻止派发到L1d缓存的微操作现在允许派发。
负载的全局可见性确实会影响其他核心的缓存一致性状态,这就是为什么我认为LFENCE
要求负载具有全局可见性的原因。核心中的负载未命中会进入LLC(最后一级缓存),LLC的嗅探过滤器显示只有另一个核心拥有该行数据。如果有1个或更多核心拥有该行数据,则需要将其降级到S状态并导致其写回修改的数据。写入LLC的数据可以带有S状态和全局可见通知返回到请求核心。如果核心中的负载未命中而LLC也未命中,则LLC可能会立即发送全局可见通知,并将请求发送给主代理从内存中获取数据(或者在多插槽系统中,LLC必须等待主代理确认不需要嗅探其他核心,然后才能向核心发送全局可见通知)。
我认为高级负载是指不再是推测性的负载,正在等待数据返回并变为有效数据,或者已经有效,因此可以立即退役,而高级负载指令是指从ROB中退役后分派的指令。
MFENCE
通过等待本地存储变为 GV 来消除第二种情况,在读取 L1 之前。” - BeeOnRope