为什么要刷新其他逻辑处理器引起的内存顺序违规的管道?

Mar*_*oom 5 x86 assembly cpu-architecture memory-barriers

vTune文档Memory Order Machine Clear性能事件描述为:

当来自另一个处理器的侦听请求与管道中数据操作的源匹配时,将发生内存排序(MO)机器清除。在这种情况下,在撤消正在进行的装载和存储之前,应清理管道。

但是我不明白为什么会这样。在不同逻辑处理器上的加载和存储之间没有同步顺序。
处理器可以假装所有当前的机上数据操作都提交进行监听。

此问题也在此处描述

每当CPU内核检测到“内存排序冲突”时,就会触发内存排序机清除。基本上,这意味着一些当前待处理的指令试图访问我们刚刚发现同时写入了其他CPU内核的内存。由于这些指令仍被标记为待处理,而“此存储器刚刚被写入”事件则表示其他某个内核已成功完成写入,因此,待处理指令以及取决于结果的所有内容都是错误的:当我们开始执行这些指令时在说明中,我们使用的内存内容版本已过时。因此,我们需要把所有工作都扔掉,然后再做完。这很清楚。

但这对我来说没有任何意义,CPU不需要重新执行Load-Queue中的装载,因为没有针对非锁定装载/存储的总订单。

我可以看到一个问题,即允许对负载进行重新排序:

;foo is 0
mov eax, [foo]    ;inst 1
mov ebx, [foo]    ;inst 2
mov ecx, [foo]    ;inst 3
Run Code Online (Sandbox Code Playgroud)

如果执行顺序为1 3 2,则mov [foo], 13至2之间的存储会导致

eax = 0
ebx = 1
ecx = 0
Run Code Online (Sandbox Code Playgroud)

这确实违反了内存排序规则。

但是负载不能随负载重新排序,那么当来自另一个内核的监听请求与任何飞行负载的来源相匹配时,为什么英特尔的CPU会刷新管道?
此行为可以防止什么错误情况?

Had*_*ais 5

尽管x86内存排序模型不允许对WC以外的任何其他内存类型的加载在程序顺序之外进行全局观察,但是该实现实际上允许加载按顺序完成。在所有先前的负载都已完成之前,暂停发出负载请求会非常昂贵。考虑以下示例:

load X
load Y
load Z
Run Code Online (Sandbox Code Playgroud)

假定第x行不在高速缓存层次结构中,而必须从内存中获取。但是,L1缓存中同时存在Y和Z。维持x86负载排序要求的一种方法是在负载X获得数据之前不发出负载Y和X。但是,这将使所有依赖Y和Z的指令停顿,从而可能会严重打击性能。

在文献中已经提出并广泛研究了多种解决方案。英特尔已在其所有处理器中实现的功能是允许按顺序发出负载,然后检查是否发生了内存排序违规,在这种情况下,将重发违反的负载并重播其所有相关指令。但是,只有在满足以下条件时,才会发生这种违规:

  • 加载已完成,而程序顺序中的上一个加载仍在等待其数据,并且这两个加载是需要排序的内存类型。
  • 另一个物理或逻辑内核已修改了后来的加载读取的行,并且在较早的加载获取数据之前,发布该加载的逻辑内核已检测到此更改。

当这两种情况同时发生时,逻辑核心将检测到内存排序冲突。考虑以下示例:

------           ------
core1            core2
------           ------
load rdx, [X]    store [Y], 1
load rbx, [Y]    store [X], 2
add  rdx, rbx
call printf
Run Code Online (Sandbox Code Playgroud)

假设初始状态为:

  • [X] = [Y] = 0。
  • 包含Y的缓存行已存在于core1的L1D中。但是X不在core1的专用缓存中。
  • X线以可修改的相干状态存在于core2的L1D中,Y线以可共享状态存在于core2的L1D中。

根据x86强排序模型,唯一可能的合法结果是0、1和3。特别是,结果2是不合法的。

可能会发生以下事件序列:

  • Core2为这两条线都发出RFO。X行的RFO将很快完成,但Y行的RFO必须一直到L3,以使core1的专用缓存中的行无效。请注意,core2只能按顺序提交存储,因此到X行的存储要等到提交Y行的存储为止。
  • Core1将两个负载发布到L1D。Y行的加载很快完成,但X行的加载需要从core2的专用缓存中获取该行。请注意,此时的Y值为零。
  • Y行从core1的专用缓存中无效,并且其在core2中的状态更改为可修改的一致性状态。
  • Core2现在按顺序提交两个存储。
  • 线X从core2转发到core1。
  • Core1从高速缓存行X加载core2存储的值为2。
  • Core1打印X和Y的总和,即0 + 2 =2。这是非法结果。本质上,core1已加载过时的值Y。

为了维持加载的顺序,core1的加载缓冲区必须将所有失效监听到驻留在其专用缓存中的行。当它检测到行Y已无效时,还有按程序顺序从无效行完成加载之前的挂起加载,则发生内存排序冲突,必须重新发出该加载,之后它才能获得最新值。请注意,如果在使其无效和从X的加载完成之前,已将行Y从core1的专用缓存中逐出,则它可能首先无法侦听行Y的无效。因此,还需要一种机制来处理这种情况。

如果core1从不使用加载的值中的一个或两个,则可能会发生加载顺序冲突,但永远无法观察到。同样,如果core2存储到X和Y行的值相同,则可能会发生负载排序冲突,但无法观察到。但是,即使在这些情况下,core1仍将不必要地重新发出违反的负载并重播其所有依赖项。

  • 有趣的事实:单个核心*可以*自行触发内存顺序错误推测。但我认为这是因为存储和加载之间的别名预测不正确,导致由于猜测加载不需要从任何先前的存储(其地址尚未准备好)转发而导致不良状态,后来发现需要存储转发。(至少我认为这就是机制。)它显示在同一个性能计数器下,但它是一个单独的东西。 (2认同)
  • @PeterCordes 这种情况称为内存消歧错误预测,其中稍后的加载被错误地预测为不依赖于先前的存储。我认为您正在考虑的事件是“MACHINE_CLEARS.MEMORY_ORDERING”,它确实计算了消歧错误预测和内存排序违规。另一种类型的消歧错误预测是 4k 混叠,但这有一个专用计数器。我所说的“发出”是指从负载缓冲区发出负载,而不是从 RS 发出负载或向 RS 发出负载。 (2认同)