内存屏障是否确保缓存一致性已完成?

Chr*_*her 16 x86 assembly operating-system memory-barriers cpu-cache

假设我有两个线程来操纵全局变量x.每个线程(或我认为的每个核心)都有一个缓存副本x.

现在说Thread A执行以下说明:

set x to 5
some other instruction
Run Code Online (Sandbox Code Playgroud)

现在set x to 5执行时,缓存的值x将设置为5,这将导致缓存一致性协议使用新值来操作和更新其他核心的缓存x.

现在我的问题是:什么时候x实际设置5Thread A缓存中,其他内核的缓存在some other instruction执行之前是否会更新?或者应该使用内存屏障来确保?:

set x to 5
memory barrier
some other instruction
Run Code Online (Sandbox Code Playgroud)

注意:假设指令是按顺序执行的,也假设set x to 5执行时,5会立即放入线程A的缓存中(因此指令不会放在队列中或稍后要执行的内容).

Mar*_*oom 19

x86体系结构中存在内存障碍 - 但这通常是正确的 - 不仅保证在执行任何后续加载或存储之前完成所有之前的1个加载或存储 - 它们还保证存储已全局可见.

通过全局可见,这意味着其他缓存感知代理(如其他CPU)可以看到商店.
其他不知道缓存的代理(如支持DMA的设备)如果目标内存标记了不强制立即写入内存的缓存类型,则通常不会看到存储.
这与它自身的障碍无关,它是x86架构的一个简单事实:程序员可以看到缓存,在处理硬件时,它们通常被禁用.

英特尔在描述障碍时是故意的,因为它不希望将自己与特定的实现联系起来.
您需要抽象地思考:全局可见意味着硬件将采取所有必要的步骤来使商店全局可见.期.

然而,要理解这些障碍,值得一看当前的实现.
请注意,只要保持可见行为正确,英特尔就可以随意将现代实现向上转换.

x86 CPU中的存储在核心中执行,然后放在存储缓冲区中.
例如mov DWORD [eax+ebx*2+4], ecx,一旦解码被停止,直到eax,ebxecx准备2然后它被调度到能够计算它的地址的执行单元.
执行完成后,存储已成为移动到存储缓冲区的对(地址,值). 该商店据说在当地完成(在核心).

存储缓冲区允许CPU的OoO部分忘记存储并考虑它已完成,即使尚未进行写入尝试.

在特定事件(如序列化事件,异常,屏障的执行或缓冲区耗尽)上,CPU会刷新存储缓冲区.
冲洗总是按顺序 - 先进先出,先写.

从商店缓冲区,商店进入缓存领域.
它可以组合到另一个名为Write Combining缓冲区的缓冲区中(后来通过缓存写入内存)如果目标地址用WC缓存类型标记,它可以写入L1D缓存,L2,如果缓存类型为WB或WT,则L3或LLC(如果它不是之前的LLC).
如果缓存类型是UC或WT,它也可以直接写入内存.


就像今天这一样,全局可见:离开商店缓冲区.
要注意两个非常重要的事情:

  1. 缓存类型仍会影响可见性.
    全局可见并不意味着在内存中可见,它意味着来自其他内核的负载可以看到它.
    如果内存区域是WB可缓存的,则加载可以在缓存中结束,因此它在那里是全局可见的 - 只有代理知道缓存的存在.(但请注意,现代x86上的大多数DMA都是缓存一致的).
  2. 这也适用于非相干的WC缓冲区.
    WC没有保持连贯性 - 其目的是将商店合并到订单无关紧要的存储区域,如帧缓冲区.这实际上并不是全局可见的,只有在写入组合缓冲区被刷新之后,核心之外的任何东西都可以看到它.

sfence确切地说:等待所有以前的商店在本地完成,然后排出商店缓冲区.
由于存储缓冲区中的每个存储都可能会遗漏,因此您会看到这样的指令有多重.(但是包括后续加载在内的无序执行可以继续.只会mfence阻止以后的加载从全局可见(从L1d缓存读取),直到存储缓冲区完成提交到缓存之后.)

但是sfence等待商店传播到其他缓存中吗?
好吧,不.
因为没有传播 - 让我们从高级角度看看写入缓存的含义.

使用MESI协议(MESIF用于多插槽Intel系统,MOESI用于AMD系统),所有处理器之间的缓存保持一致.
我们只会看到MESI.

假设写入对高速缓存行L进行索引,并假设所有处理器在其高速缓存中具有相同值的该行L. 在每个CPU中
,此行的状态为Shared.

当我们的商店登陆缓存时,L被标记为已修改,并且在内部总线(或多插槽Intel系统的QPI)上进行特殊处理,以使其他处理器中的L行无效.

如果L不是最初在小号状态时,协议被相应地改变(例如,如果大号处于状态独占总线上没有交易完成[ 1 ]).

此时写入完成并sfence完成.

这足以使缓存保持一致.
当另一个CPU请求行L时,我们的CPU侦听该请求,L被刷新到内存或内部总线,因此另一个CPU将读取更新的版本.
L的状态再次 设置为S.

因此基本上L是按需读取的 - 这是有道理的,因为将写入传播到其他CPU是昂贵的,一些架构通过将L写回内存来实现(这是因为其他CPU的L状态为无效,因此它必须从记忆).


最后,并非sfence所有人通常都是无用的,相反,它们非常有用.
通常我们并不关心其他CPU如何看待我们制作我们的商店 - 但是如果在C++中定义获取语​​义而没有获取语​​义,并且使用围栏实现锁定,则完全是疯了.

您应该考虑英特尔所说的障碍:它们强制执行内存访问的全局可见性顺序.
您可以通过将障碍视为强制执行订单或写入缓存来帮助您自我理解.然后,高速缓存一致性将确保对高速缓存的写入是全局可见的.

我不禁要再次强调缓存一致性,全局可见性和内存排序是三个不同的概念.
第一个保证第二个,由第三个强制执行.

Memory ordering -- enforces --> Global visibility -- needs -> Cache coherency
'.______________________________'_____________.'                            '
                 Architectural  '                                           '
                                 '._______________________________________.'
                                             micro-architectural
Run Code Online (Sandbox Code Playgroud)

脚注:

  1. 按程序顺序.
  2. 那是一种简化.在Intel CPU上,mov [eax+ebx*2+4], ecx解码为两个单独的uop:store-address和store-data.存储地址uop必须等到eaxebx准备就绪,然后将其分派给能够计算其地址的执行单元.该执行单元将地址写入存储缓冲区,因此稍后加载(按程序顺序)可以检查存储转发.

    ecx准备好了,店里数据UOP可以分派到存储数据端口,并将数据写入同一个存储缓冲项.

    这可以在地址已知之前或之后发生,因为存储缓冲区条目可能按程序顺序保留,因此存储缓冲区(也称为内存顺序缓冲区)可以在最终知道所有内容的地址后跟踪加载/存储顺序,并检查重叠.(对于最终违反x86的内存排序规则的推测性负载,如果另一个核心使他们从最早的点之前加载的高速缓存行失效,那么它们在架构上被允许放置.这会导致内存顺序错误推测管道清晰.)

  • 使用“商店已完成”这样的术语是上述混乱的一部分。它可以解释。我可以说,当存储在 ROB 中不再推测时,或者当它到达 DRAM 时,或者当它到达磁盘上的内存映射文件等时,存储就完成了。“SFENCE”不会耗尽存储缓冲区!`SFENCE` 仅适用于某些绕过存储缓冲区的“奇怪”类型的存储。存储缓冲区本身本质上是有序的:这是它首先存在的一个重要原因(也是为了杀死投机存储)。 (2认同)
  • @BeeOnRope 等等...你是说英特尔使用术语“存储缓冲区”的第 11.10 节实际上应该读作“WC 缓冲区”吗?感谢这些链接,我不知道 NT 移入/移出 WC 内存类型是弱排序的(除了缓存绕过)!无论如何,我没有发现任何证据表明`sfence` 实际上不会耗尽 SB。承认它对重新排序普通商店没用,仅此一点并不意味着`sfence` 没有辅助功能(即排序+可见性) 我的 Fog 版本的 inst 表没有列出围栏的延迟。老实说,我很困惑…… IDK 在想什么。 (2认同)
  • 好的,现在我正确地读取了 11.10。不知何故,我之前读过 11.3.1(其中确实谈到了 WC 缓冲区)。你知道吗?我在一定程度上混淆了“SFENCE”性能和“LFENCE”性能,因此 Ryzen 上没有“1 个周期“SFENCE””——那里需要 20c。更多[数字](http://users.atw.hu/instlatx64/)。所以是的,我认为你实际上是对的:“SFENCE”需要耗尽存储缓冲区和 WC 缓冲区才能完成其工作,否则它如何保证“[正常存储,sfence,弱存储]”被正确排序? (2认同)

Bre*_*dan 5

现在,当执行set x到5时,x的缓存值将设置为5,这将导致缓存一致性协议使用x的新值执行并更新其他内核的缓存.

有多个不同的x86 CPU具有不同的缓存一致性协议(无,MESI,MOESI),以及不同类型的缓存(未缓存,写入组合,只写,直写,回写).

通常在执行写操作时(将x设置为5时),CPU确定正在执行的缓存类型(来自MTRR或TLB),如果缓存行可以缓存,则会检查自己的缓存以确定缓存的状态线是(从它自己的角度来看).

然后使用缓存类型和缓存行的状态来确定数据是否直接写入物理地址空间(绕过缓存),或者是否必须从其他地方获取缓存行,同时告知其他CPU无效旧副本,或者如果它在自己的缓存中具有独占访问权限,并且可以在缓存中修改它而不会告诉任何内容.

CPU永远不会将数据"注入"到另一个CPU的缓存中(并且只告诉其他CPU使其缓存行的副本无效/丢弃).告诉其他CPU无效/丢弃它们的缓存行副本会导致它们在需要时再次获取它的当前副本.

请注意,这些都与内存障碍无关.

有3种类型的内存屏障(sfence,lfencemfence),它告诉CPU后,允许存储,负载或两者发生前完成门店,负载或两者兼而有之.因为CPU通常是高速缓存一致的,所以这些内存屏障/栅栏通常是没有意义/不必要的.然而,存在CPU不是高速缓存一致的情况(包括"存储转发",当使用写入组合高速缓存类型时,当使用非时间存储时等).需要内存屏障/围栏来强制执行这些特殊/罕见情况的排序(如有必要).

  • 你回答问题的关键点(MESI/MOESI不会将数据推送到其他缓存中,因此OP问题形成不良 - 无需等待任何事情完成)但最后一段是错误的.您将内存排序与缓存一致性混淆.一旦进入缓存,至少对于x86系统,数据是全局可见的.但是由于重新排序和存储缓冲区,商店变得全局可见的时间不是按程序顺序或在商店完成时 - >因此存在障碍. (3认同)