我想知道的是,如果 lock xchg
与一个线程访问一个正在被其他线程进行变异的内存位置(我们只是随机说)的角度,它将具有与 mfence
类似的行为 . 它能保证我获得最新的 Value 吗?之后的内存读/写指令?
我混淆的原因是:
8.2.2“读取或写入不能使用I / O指令,锁定指令或序列化指令进行重新排序 . ” - 英特尔64开发人员手册卷 . 3
这是否适用于线程?
mfence
州:
对MFENCE指令之前发出的所有内存加载和存储到内存指令执行序列化操作 . 此序列化操作保证在MFENCE指令之前的任何加载或存储指令全局可见之前,在程序顺序之前的每条加载和存储指令都是全局可见的 . MFENCE指令针对所有加载和存储指令,其他MFENCE指令,任何SFENCE和LFENCE指令以及任何序列化指令(例如CPUID指令)进行排序 . -Intel 64 Developers Manual Vol 3A
这听起来更有力 . 听起来像 mfence
几乎正在刷新写缓冲区,或者至少延伸到写缓冲区和其他内核以确保我未来的加载/存储是最新的 .
当基准标记时,两个指令都需要约100个循环才能完成 . 所以我无论如何都看不出那么大的差异 .
主要是我只是困惑 . 我的指令基于 lock
在互斥体中使用,但是这些包含没有内存栅栏 . 然后我看到使用内存防护但没有锁的无锁编程 . 我知道AMD64有一个非常强大的内存模型,但过时的值可以在缓存中持续存在 . 如果 lock
的行为与 mfence
的行为不同,那么互斥锁如何帮助您查看最新值?
1 回答
我相信你的问题与询问
mfence
是否与x86上的lock
-prefixed指令具有相同的屏障语义,或者在某些情况下它是否提供更少1或额外保证相同 .我目前最好的答案是,这是英特尔的意图,并且ISA文档保证
mfence
和lock
ed指令提供相同的防护语义,但由于实现疏忽,mfence
实际上在最近的硬件上提供了更强的防护语义(至少从Haswell开始) . 特别是,mfence
可以阻止来自WC类型存储器区域的后续非临时负载,而lock
ed指令则不会 .我们知道这一点,因为英特尔在处理器勘误中告诉我们这一点,例如HSD162 (Haswell)和SKL155 (Skylake),它告诉我们锁定的指令不会阻止从WC内存的后续非临时读取:
由此,我们可以确定(1)英特尔可能打算从WC类型的内存中锁定NT加载的锁定指令,否则这不是errata0.5和(2)锁定指令实际上不会这样做,并且Intel无法或选择不使用微代码更新来修复此问题,建议使用
mfence
.在Skylake中,
mfence
实际上失去了相对于NT负载的额外防护能力,如 SKL079: MOVNTDQA From WC Memory May Pass Earlier MFENCE Instructions - 这与lock
-instruction勘误表几乎相同,但适用于mfence
. 但是,这个勘误表的状态是"It is possible for the BIOS to contain a workaround for this erratum.",通常是英特尔代言"a microcode update addresses this" .这个勘误序列也许可以用时间来解释:Haswell勘误表只出现在2016年初,即该处理器发布后的几年,所以我们可以假设这个问题在此之前的适当时间内引起了英特尔的注意 . 在这一点上,Skylake几乎肯定已经出局了,显然不那么保守了
mfence
实现也没有在WC类型的内存区域上屏蔽NT负载 . 修复锁定指令一直工作到Haswell的方式可能要么根本不可能或昂贵,基于它们的广泛使用,但需要一些方法来限制NT负载 .mfence
显然已经完成了Haswell的工作,Skylake将被修复,以便mfence
也在那里工作 .它并没有真正解释为什么SKL079(
mfence
one)出现在2016年1月,距离SKL155(locked
one)出现在2017年末近两年,或者为什么后者在完全相同的Haswell勘误之后出现了这么多 .人们可能会猜测英特尔将来会做些什么 . 由于他们无法/愿意通过Skylake更改Haswell的指令,代表数亿(数十亿?)已部署的芯片,他们可能会依赖这个十年或更长时间,直到芯片与当前非围栏行为几乎没有流通 .
与Haswell类似,根据BV116和BJ138,NT负载可能分别通过Sandy Bridge和Ivy Bridge上的早期锁定指令 . 早期的微体系结构也可能会遇到这个问题 . 这个"bug"似乎并不存在于Skylake之后的Broadwell和微体系结构中 .
彼得·科德斯(Peter Cordes)在this answer结束时写了一篇关于Skylake
mfence
变化的文章 .在我知道勘误表之前,这个答案的剩余部分是我的原始答案,而这主要是出于历史兴趣 .
旧答案
我对答案的猜测是,
mfence
提供了额外的屏障功能:在使用弱有序指令(例如,NT存储)的访问之间以及可能在访问弱有序区域(例如,WC类型存储器)之间 .也就是说,这只是一个明智的猜测,你会在下面找到我的调查细节 .
详情
文档
mfence
的内存一致性效果与lock
-prefixed指令(包括xchg
与内存操作数,隐式锁定)提供的内容一致性影响的程度并不十分清楚 .我认为可以肯定地说,仅仅针对回写内存区域而不涉及任何非时间访问,
mfence
提供与lock
-prefixed操作相同的排序语义 .有争议的是,当涉及到上述情况之外的情况时,
mfence
是否与lock
-前缀指令完全不同,特别是当访问涉及WB区域以外的区域或涉及非时间(流)操作时 .例如,您可以找到一些建议(例如here或here),当涉及WC类型的操作(例如,NT存储)时,
mfence
暗示强屏障语义 .例如,在_2906941中引用McCalpin博士(重点补充):
我们来看看英特尔SDM的参考部分8.2.5:
与麦卡尔平博士的解释2相反,我认为这一部分有点模棱两可,以至于
mfence
是否做了额外的事情 . 涉及IO,锁定指令和序列化指令的三个部分确实意味着它们在操作之前和之后的存储器操作之间提供了完全屏障 . 它们对于弱有序的存储器没有任何例外,并且在IO指令的情况下,人们还假设它们需要以弱有序的存储区域以一致的方式工作,因为这些通常用于IO .然后是
FENCE
指令的部分,它明确提到弱内存区域:"The SFENCE, LFENCE, and MFENCE instructions **provide a performance-efficient way of ensuring load and store memory ordering between routines that produce weakly-ordered results and routines that consume that data."我们是否在这些行之间进行了阅读,并认为这些是完成此操作的唯一指令,并且前面提到的技术(包括锁定指令)对弱内存区域没有帮助?我们可以通过注意围栅指令与弱序非临时存储指令同时引入3以及 11.6.13 Cacheability Hint Instructions 中专门处理弱有序指令的文本来找到对此思想的一些支持:
同样,这里的围栏说明是具体的提到适合于弱有序的指令 .
我们还发现支持这样一种观点,即锁定指令可能不会在上面已经引用的最后一个句子的弱有序访问之间提供障碍:
这基本上意味着
FENCE
指令实质上取代了先前在内存排序方面序列化cpuid
提供的功能 . 但是,如果lock
-prefixed指令提供了与cpuid
相同的屏障功能,那么这可能是之前建议的方式,因为它们通常比cpuid
快得多,后者通常需要200个或更多个周期 . 这意味着有些场景(可能是弱排序的场景)lock
-prefixed指令没有处理,并且cpuid
正被使用,并且mfence
现在被建议作为替换,暗示比lock
-prefixed指令更强的屏障语义 .但是,我们可以用不同的方式解释上面的一些内容:请注意,在围栏指令的上下文中,经常提到它们是确保排序的性能有效方式 . 因此,这些说明可能并非旨在提供额外的障碍,而只是提供更有效的障碍 .
实际上,在几个周期内的
sfence
比串行化指令要快得多,例如通常为20个周期或更长的cpuid
或lock
-前缀指令 . 另一方面,mfence
通常不比锁定指令快4,至少在现代硬件上是这样 . 尽管如此,它在引入或未来某些设计时可能会更快,或者预计它会更快但但并没有成功 .所以我不能根据手册的这些部分做出一定的评估:我认为你可以做出合理的论证,可以用任何一种方式解释它 .
我们可以进一步查看英特尔ISA指南中各种非临时存储指令的文档 . 例如,在非临时存储
movnti
的文档中,您会找到以下引用:关于"if multiple processors might use different memory types to read/write the destination memory locations"的部分对我来说有点混乱 . 我希望这可以说像"to enforce ordering in the globally visible write order between instructions using weakly ordered hints"之类的东西 . 实际上,实际的存储器类型(例如,如MTTR所定义的)可能在这里甚至不起作用:当使用弱有序指令时,排序问题可能仅在WB存储器中出现 .
表现
据报道,
mfence
指令在现代CPU上基于Agner fog的指令时序需要33个周期(背靠背延迟),但据报道,像lock cmpxchg
这样的更复杂的锁定指令只需要18个周期 .如果
mfence
提供的障碍语义不比lock cmpxchg
强,后者正在做更多的工作,并且没有明显的理由让mfence
花费更长的时间 . 当然你可以说lock cmpxchg
比mfence
更重要,因此可以获得更多优化 . 由于所有锁定的指令都比mfence
快得多,甚至不经常使用的指令,因此这一论点被削弱了 . 此外,您可以想象如果所有lock
指令共享一个屏障实现,mfence
将只使用相同的那个,因为它是最简单和最容易验证的 .因此,在我看来,
mfence
的较慢表现是mfence
做了一些额外的重要证据 .0.5这不是一个不透水的论点 . 有些东西可能会出现在勘误表中,显然是"by design"而不是错误,例如
popcnt
对目标寄存器的错误依赖 - 所以一些勘误表可以被认为是一种更新期望的文档形式,而不是总是暗示硬件错误 .1显然,
lock
-prefixed指令也执行原子操作,这是不可能单独用mfence
实现的,因此lock
-prefixed指令肯定具有附加功能 . 因此,对于mfence
有用,我们会期望它在某些情况下要么具有额外的屏障语义,要么表现得更好 .2他完全有可能阅读散文不同的手册的不同版本 .
SSE中为
SFENCE
,SSE2中为lfence
和mfence
.4通常它的速度较慢:Agner在最近的硬件上列出了33个周期的延迟,而锁定指令通常约为20个周期 .