假设我有两个操作全局变量x
的线程。每个线程(或我认为的每个核心)都有一个x
的缓存副本。
现在说Thread A
执行以下说明:
set x to 5
some other instruction
现在执行set x to 5
时,x
的缓存值将设置为5
,这将导致缓存一致性协议采取行动并更新其他核心的缓存x
的新值。
现在我的问题是:当x
实际设置为5
缓存中的Thread A
时,其他核心的缓存是否会在{{1}之前更新被执行?或者应该使用内存屏障来确保?:
some other instruction
注意: 假设说明按顺序执行,并假设执行set x to 5
memory barrier
some other instruction
时,set x to 5
立即执行放在线程A的缓存中(因此指令没有放在队列中或稍后要执行的东西)。
答案 0 :(得分:17)
x86体系结构中存在内存障碍 - 但总的来说这是正确的 - 不仅保证所有先前的 1 加载或存储在任何后续加载或存储执行之前完成 - 他们还保证商店已成为全球可见。
通过全局可见,这意味着其他缓存感知代理(如其他CPU)可以看到商店
其他不知道缓存的代理 - 就像支持DMA的设备 - 如果目标内存标记的缓存类型没有强制立即写入内存,则通常不会看到存储。
这与它自身的障碍无关,这是x86架构的一个简单事实:程序员可以看到缓存,在处理硬件时,它们通常被禁用。
英特尔在描述障碍时是故意的,因为它并不想将自己与特定的实施联系起来。
您需要抽象地思考:全局可见意味着硬件将采取所有必要的步骤来使商店全局可见。周期。
要了解障碍,请务必查看当前的实施情况 请注意,只要保持可见行为正确,英特尔就可以随意将现代实现向上转换。
x86 CPU中的存储在核心中执行,然后放在存储缓冲区中。
例如mov DWORD [eax+ebx*2+4], ecx
,一旦解码就会停止,直到eax
,ebx
和ecx
准备就绪 2 ,然后将其分派给能够执行的执行单元计算其地址
执行完成后,存储已成为一对(地址,值),它将移动到存储缓冲区。
该商店被称为在本地完成(在核心)。
存储缓冲区允许CPU的OoO部分忘记存储并考虑它已完成,即使还没有进行写入尝试。
在特定事件(如序列化事件,异常,执行屏障)或缓冲区耗尽时,CPU会刷新存储缓冲区。
同花顺总是有序的 - 先进先出,先写。
从商店缓冲区,商店进入缓存领域
它可以组合到另一个称为写入组合缓冲区的缓冲区中(如果目标地址标记为WC缓存类型,则稍后将其写入内存中),它可以写入如果缓存类型为WB或WT,则L1D缓存,L2,L3或LLC(如果它不是之前的那个)。
如果缓存类型是UC或WT,它也可以直接写入内存。
就像今天一样,全局可见:离开商店缓冲区。
谨防两件非常重要的事情:
sfence
完全相同:等待所有以前的商店在本地完成,然后排空商店缓冲区。
由于存储缓冲区中的每个存储都可能会遗漏,因此您会看到这样的指令有多重。 (但是包括后续加载在内的无序执行可以继续。只有mfence
才会阻止后续加载从全局可见(从L1d缓存读取),直到存储缓冲区完成提交缓存为止。)
但是sfence
等待商店传播到其他缓存中吗?
好吧,没有。
因为没有传播 - 让我们从高级角度看看写入缓存的含义。
使用MESI协议(MESIF用于多插槽Intel系统,MOESI用于AMD系统),所有处理器之间的缓存保持一致。 我们只会看到MESI。
假设写入对高速缓存行L进行索引,并假设所有处理器在其高速缓存中具有相同值的此行L. 在每个CPU中,此行的状态为 Shared 。
当我们的商店登陆缓存时,L被标记为已修改,并且在内部总线(或多插槽Intel系统的QPI)上进行特殊处理,以使其他处理器中的L行无效。
如果L最初不在 S 状态,则协议会相应更改(例如,如果 L 处于状态独占,则无任何事务处理总线完成 [1] )。
此时写入完成,sfence
完成。
这足以保持缓存的连贯性
当另一个CPU请求行L时,我们的CPU侦听该请求并将L刷新到内存或内部总线,以便其他CPU读取更新后的版本。
L的状态再次设置为 S 。
所以基本上L是按需读取的 - 这是有道理的,因为将写入传播到其他CPU是昂贵的,一些架构通过将L写回内存来实现(这是因为其他CPU在状态无效所以必须从内存中读取它。
最后,sfence
所有人通常都是无用的,相反,它们非常有用。
通常我们并不关心其他CPU如何看待我们制作我们的商店 - 而是在没有获取语义的情况下获取锁定(例如,在C ++中),并使用围栏实现,完全疯了。
你应该想到英特尔所说的障碍:它们强制执行内存访问的全局可视性顺序 您可以通过将障碍视为强制执行订单或写入缓存来帮助您自我理解。然后,缓存一致性将确保对缓存的写入全局可见。
我无法帮助,但再次强调缓存一致性,全局可见性和内存排序是三个不同的概念。
第一个保证第二个,由第三个强制执行。
Memory ordering -- enforces --> Global visibility -- needs -> Cache coherency
'.______________________________'_____________.' '
Architectural ' '
'._______________________________________.'
micro-architectural
脚注:
这是一种简化。在Intel CPU上,mov [eax+ebx*2+4], ecx
解码为两个单独的uop:store-address和store-data。存储地址uop必须等到eax
和ebx
准备就绪,然后将其分派给能够计算其地址的执行单元。该执行单元writes the address into the store buffer,因此稍后加载(按程序顺序)可以检查存储转发。
当ecx
准备就绪时,store-data uop可以调度到store-data端口,并将数据写入相同的存储缓冲区条目。
这可能发生在地址已知之前或之后,因为存储缓冲区条目可能按程序顺序保留,因此存储缓冲区(也称为内存顺序缓冲区)可以跟踪加载/存储顺序一旦所有地址最终是已知的,并检查重叠。 (对于最终违反x86的内存排序规则的推测性负载,如果另一个核心使他们从最早的点之前加载的高速缓存行失效,那么他们在架构上被允许使用。这导致a memory-order mis-speculation pipeline clear。)
答案 1 :(得分:5)
现在,当执行set x to 5时,x的缓存值将设置为 5,这将导致缓存一致性协议动作和更新 使用新值x的其他核心的缓存。
有多个不同的x86 CPU具有不同的缓存一致性协议(无,MESI,MOESI),以及不同类型的缓存(未缓存,写入组合,只写,直写,回写)。
通常在执行写操作时(将x设置为5时),CPU会确定正在执行的缓存类型(来自MTRR或TLB),如果缓存行可以缓存,则会检查自己的缓存以确定说明缓存行是在(从它自己的角度来看)。
然后使用缓存类型和缓存行的状态来确定数据是否直接写入物理地址空间(绕过缓存),或者是否必须从其他地方获取缓存行,同时告诉其他CPU使旧副本无效,或者它在自己的高速缓存中具有独占访问权限,并且可以在高速缓存中修改它而不会告诉任何内容。
CPU永远不会注入"数据到另一个CPU的缓存中(并且只告诉其他CPU使其缓存行的副本无效/丢弃)。告诉其他CPU无效/丢弃他们的缓存行副本会导致他们在需要时再次获取它的当前副本。
请注意,这些都与内存障碍无关。
有3种类型的内存屏障(sfence
,lfence
和mfence
),它们告诉CPU在允许以后的存储,加载或两者之前完成存储,加载或两者发生。因为CPU通常是高速缓存一致的,所以这些内存屏障/栅栏通常是没有意义/不必要的。然而,存在CPU不是高速缓存一致性的情况(包括"存储转发",当使用写组合高速缓存类型时,当使用非时间存储时等)。需要使用内存屏障/围栏来强制执行这些特殊/罕见情况的排序(如有必要)。
答案 2 :(得分:2)
否,内存屏障绝对不能确保缓存一致性已“完成”。它通常不涉及所有的连贯性操作,可以推测性地执行或作为无操作执行。
它仅强制执行屏障中描述的排序语义。例如,实现可能只是在存储队列中放置一个标记,使得对于早于标记的存储不会发生存储到加载转发。
特别是,英特尔已经拥有一个强大的内存模型,用于正常的加载和存储(编译器生成的类型以及您在汇编中使用的那种),其中唯一可能的重新排序是稍后通过早期存储的负载。在SPARC记忆障碍的术语中,StoreLoad
以外的每个障碍已经是no-op。
实际上,x86上的有趣的障碍附加到LOCKed
指令,并且执行这样的指令根本不一定涉及任何高速缓存一致性。如果该行已经处于独占状态,则CPU可以简单地执行该指令,确保在操作正在进行时(即,在读取参数和回写结果之间)不释放该行的独占状态。然后只处理防止存储到转载转发违反LOCK
指令带来的总排序。目前他们通过耗尽商店队列来做到这一点,但在未来的处理器中甚至可能是推测性的。
内存屏障或障碍+操作是什么确保其他代理以相对顺序看到操作,该服务遵守屏障的所有限制。当然,这通常不会将将结果推送到其他CPU作为一致性操作,如您所暗示的那样。
答案 3 :(得分:-1)
如果没有其他处理器在其缓存中具有X,则在处理器A上执行x = 5不会更新任何其他处理器中的缓存。如果处理器B读取变量X,则处理器A将检测到读取(称为侦听)并在总线上为处理器B提供数据5。现在,处理器B在其高速缓存中的值为5。如果没有其他处理器读取变量X,则将永远不会使用新值5更新其缓存。