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会刷新管道?
此行为可以防止什么错误情况?
尽管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)
假设初始状态为:
根据x86强排序模型,唯一可能的合法结果是0、1和3。特别是,结果2是不合法的。
可能会发生以下事件序列:
为了维持加载的顺序,core1的加载缓冲区必须将所有失效监听到驻留在其专用缓存中的行。当它检测到行Y已无效时,还有按程序顺序从无效行完成加载之前的挂起加载,则发生内存排序冲突,必须重新发出该加载,之后它才能获得最新值。请注意,如果在使其无效和从X的加载完成之前,已将行Y从core1的专用缓存中逐出,则它可能首先无法侦听行Y的无效。因此,还需要一种机制来处理这种情况。
如果core1从不使用加载的值中的一个或两个,则可能会发生加载顺序冲突,但永远无法观察到。同样,如果core2存储到X和Y行的值相同,则可能会发生负载排序冲突,但无法观察到。但是,即使在这些情况下,core1仍将不必要地重新发出违反的负载并重播其所有依赖项。