![](/img/trans.png)
[英]Does `xchg` encompass `mfence` assuming no non-temporal instructions?
[英]Does lock xchg have the same behavior as mfence?
我想知道的是,如果一个线程访问一个正在被其他线程突变的内存位置(让我们随便说),那么lock xchg
会对mfence
产生类似的行为。 它能保证我获得最新的价值吗? 之后的内存读/写指令?
我混淆的原因是:
8.2.2“读取或写入不能通过I / O指令,锁定指令或序列化指令重新排序。”
-Intel 64 Developers Manual Vol。 3
这是否适用于线程?
mfence
说:
对MFENCE指令之前发出的所有内存加载和存储到内存指令执行序列化操作。 此序列化操作保证在MFENCE指令之前的任何加载或存储指令全局可见之前,在程序顺序之前的每条加载和存储指令都是全局可见的。 MFENCE指令针对所有加载和存储指令,其他MFENCE指令,任何SFENCE和LFENCE指令以及任何序列化指令(例如CPUID指令)进行排序。
-Intel 64 Developers Manual Vol 3A
这听起来更有力。 因为听起来mfence
几乎正在mfence
写写缓冲区,或者至少延伸到写缓冲区和其他内核以确保我未来的加载/存储是最新的。
当基准标记时,两个指令都需要约100个循环才能完成。 所以我无论如何都看不出那么大的差异。
主要是我只是困惑。 我的指令基于互斥lock
使用的锁,但是这些包含没有内存栅栏。 然后,我看到锁使用内存栅栏自由编程,但没有锁。 我知道AMD64有一个非常强大的内存模型,但过时的值可以在缓存中持续存在。 如果lock
的行为与mfence
行为不同,那么互斥锁如何帮助您查看最新值?
我相信你的问题与询问mfence
是否具有与x86上的lock
前缀指令相同的屏障语义,或者它是否在某些情况下提供更少的1或额外保证相同。
我目前最好的答案是,这是英特尔的意图 ,并且ISA文档保证mfence
和lock
指令提供相同的防护语义,但由于实现疏忽, mfence
实际上在最近的硬件上提供了更强的防护语义(至少从Haswell开始) 。 特别是, mfence
可以mfence
来自WC型存储区域的后续非临时负载 ,而lock
指令则不会。
我们知道这一点,因为英特尔在处理器勘误中告诉我们这一点,例如HSD162(Haswell)和SKL155(Skylake) ,它告诉我们锁定的指令不会阻止从WC内存的后续非时间读取:
来自WC内存的MOVNTDQA可能会通过更早的锁定指令
问题:从WC(写入组合)存储器加载的(V)MOVNTDQA(流加载指令)的执行可能看起来通过访问不同高速缓存行的较早锁定指令。
含义:期望锁定后续(V)MOVNTDQA指令的软件可能无法正常运行。
解决方法:未确定。 依赖于锁定指令来阻止后续执行(V)MOVNTDQA的软件应在锁定指令和后续(V)MOVNTDQA指令之间插入MFENCE指令。
由此,我们可以判断:(1)英特尔可能是打算从WC型内存锁定指令围栏NT负载,否则这不会是一个勘误表0.5(2)锁定指令实际上没有这样做,英特尔无法或不选择使用微代码更新来修复此问题,建议使用mfence
。
在Skylake中, mfence
实际上失去了相对于NT负载的附加防护能力,根据SKL079:来自WC内存的MOVNTDQA可以通过早期的MFENCE指令 - 这与lock
-instruction勘误表几乎相同,但适用于mfence
。 但是,这个勘误表的状态是“BIOS可能包含此错误的解决方法。”,这通常是英特尔所说的“微代码更新解决了这个问题”。
这个勘误序列也许可以用时间来解释:Haswell勘误表只出现在2016年初,即该处理器发布后的几年,所以我们可以假设这个问题在此之前的适当时间内引起了英特尔的注意。 在这一点上,Skylake几乎可以肯定已经出现在野外,显然是一个不太保守的mfence
实现,也没有在WC类型的内存区域mfence
NT负载。 修复锁定指令一直工作到Haswell的方式可能要么根本不可能或昂贵,基于它们的广泛使用,但需要一些方法来限制NT负载。 mfence
显然已经完成了Haswell的工作,Skylake将被修复,以便mfence
也在那里工作。
它并没有真正解释为什么SKL079( mfence
one)出现在2016年1月,差不多两年之前SKL155( locked
一个)出现在2017年底,或者为什么后者在完全相同的Haswell勘误之后出现了这么多。
人们可能会猜测英特尔将来会做些什么。 由于他们无法/愿意通过Skylake更改Haswell的lock
指令,代表数亿(数十亿?)已部署的芯片,他们永远无法保证锁定的指令可以阻止NT加载,因此他们可能会考虑制作这是未来记录的架构行为。 或者他们可能会更新锁定的指令,因此他们确实对这些读取进行了限制,但实际上你可能不会依赖这十年或更长时间,直到具有当前非击剑行为的筹码几乎没有流通。
与Haswell类似,根据BV116和BJ138 ,NT负载可能分别通过Sandy Bridge和Ivy Bridge上的早期锁定指令。 早期的微体系结构也可能会遇到这个问题。 在Skylake之后,Broadwell和微体系结构中似乎不存在这个“错误”。
Peter Cordes在这个答案的最后写了一些关于Skylake mfence
变化的文章。
在我知道勘误表之前,这个答案的剩余部分是我的原始答案,而这主要是出于历史兴趣。
我对答案的猜测是, mfence
提供了额外的屏障功能:在使用弱有序指令(例如,NT存储)的访问之间以及可能在访问弱有序区域 (例如,WC类型存储器)之间。
也就是说,这只是一个明智的猜测,你会在下面找到我的调查细节。
mfence
的内存一致性影响与lock
-prefixed指令(包括xchg
与内存操作数,隐式锁定)提供的程度并不完全清楚。
我认为可以肯定地说,仅仅针对回写内存区域而不涉及任何非时间访问, mfence
提供与lock
前缀操作相同的排序语义。
可以讨论的是,当涉及到上述情况之外的场景时,mfence是否与lock
前缀指令mfence
不同,特别是当访问涉及WB区域以外的区域或涉及非时间(流)操作时。
例如,您可以找到一些建议(例如此处或此处 ),当涉及WC类型的操作(例如,NT存储)时, mfence
意味着强屏障语义。
例如,在这个帖子中引用麦卡尔平博士(重点补充):
围栏指令仅需要绝对确保所有非临时存储在随后的“普通”存储之前是可见的。 最重要的情况是并行代码,其中并行区域末端的“屏障”可能包括“普通”存储。 如果没有围栅,处理器可能仍然在写入组合缓冲区中修改了数据,但是通过屏障并允许其他处理器读取写入组合数据的“陈旧”副本。 此方案也可能适用于操作系统从一个核心迁移到另一个核心的单个线程(不确定此情况)。
我不记得详细的推理(今天早上咖啡还不够),但是你想要在非临时商店之后使用的指令是MFENCE。 根据SWDM第3卷第8.2.5节,MFENCE是唯一一个防止后续加载和后续存储在完成栅栏之前执行的栅栏指令。 令我感到惊讶的是,第11.3.1节没有提到这一点,它告诉你在使用写入组合时手动确保一致性是多么重要,但是没有告诉你如何做到这一点!
我们来看看英特尔SDM的参考部分8.2.5:
加强或弱化记忆订购模型
英特尔64和IA-32架构提供了多种机制来加强或削弱内存排序模型,以处理特殊编程情况。 这些机制包括:
•I / O指令,锁定指令,LOCK前缀和序列化指令强制处理器的排序更强。
•SFENCE指令(引入Pentium III处理器中的IA-32架构)和LFENCE和MFENCE指令(Pentium 4处理器中引入)为特定类型的存储器操作提供了存储器排序和序列化功能。
这些机制可以使用如下:
总线上的存储器映射设备和其他I / O设备通常对写入其I / O缓冲区的顺序很敏感。 I / O指令可用于(IN和OUT指令)对此类访问强加写入顺序,如下所示。 在执行I / O指令之前,处理器等待程序中的所有先前指令完成,并且所有缓冲写入都要耗尽到存储器。 只有指令获取和页表行走才能通过I / O指令。 在处理器确定I / O指令已完成之前,不会开始执行后续指令。
多处理器系统中的同步机制可能依赖于强存储器排序模型。 这里,程序可以使用诸如XCHG指令或LOCK前缀之类的锁定指令来确保对存储器的读取 - 修改 - 写入操作以原子方式执行。 锁定操作通常像I / O操作一样操作,因为它们等待所有先前的指令完成,并且所有缓冲的写操作都要耗尽到存储器(参见第8.1.2节“总线锁定”)。
也可以使用序列化指令执行程序同步(参见第8.3节)。 这些指令通常用于关键过程或任务边界,以在跳转到新的代码段或上下文切换之前强制完成所有先前的指令。 与I / O和锁定指令一样,处理器等待所有先前的指令完成,并且在执行序列化指令之前已将所有缓冲的写入耗尽到存储器。
SFENCE,LFENCE和MFENCE指令提供了一种性能有效的方法,可确保在生成弱排序结果的例程和使用该数据的例程之间加载和存储内存顺序 。 这些说明的功能如下:
•SFENCE - 序列化程序指令流中SFENCE指令之前发生的所有存储(写入)操作,但不影响加载操作。
•LFENCE - 序列化程序指令流中LFENCE指令之前发生的所有加载(读取)操作,但不影响存储操作。
•MFENCE - 序列化程序指令流中MFENCE指令之前发生的所有存储和加载操作。
请注意,SFENCE,LFENCE和MFENCE指令提供了一种比CPUID指令更有效的控制内存排序的方法。
与麦卡尔平博士的解释2相反,我认为这一部分对于mfence
是否做了额外的事情有些含糊不清。 涉及IO,锁定指令和序列化指令的三个部分确实意味着它们在操作之前和之后的存储器操作之间提供了完全屏障。 它们对于弱有序的存储器没有任何例外,并且在IO指令的情况下,人们还假设它们需要以弱有序的存储区域以一致的方式工作,因为这些通常用于IO。
然后是FENCE
指令的部分,它明确提到弱内存区域:“SFENCE,LFENCE和MFENCE指令**提供了一种性能有效的方法来确保在生成弱有序结果和例程的例程之间加载和存储内存排序消耗这些数据。“
我们是否在这些行之间进行了阅读,并认为这些是完成此操作的唯一指令,并且前面提到的技术(包括锁定指令)对弱内存区域没有帮助? 我们可以通过注意围栏指令与弱序非时态存储指令同时引入3以及11.6.13 Cacheability Hint指令中专门针对弱有序指令处理的文本来找到对此思想的一些支持:
数据消费者知道数据被弱排序的程度可能因这些情况而异。 因此,应使用SFENCE或MFENCE指令来确保生成弱排序数据的例程和使用数据的例程之间的排序。 SFENCE和MFENCE通过保证程序顺序中SFENCE / MFENCE之前的每个商店指令在跟随围栏的商店指令之前是全局可见的,提供了一种性能有效的方式来确保订购。
同样,这里特别提到围栅指令适用于屏蔽弱有序指令。
我们还发现支持这样一种观点,即锁定指令可能不会在上面已经引用的最后一个句子的弱有序访问之间提供障碍:
请注意,SFENCE,LFENCE和MFENCE指令提供了一种比CPUID指令更有效的控制内存排序的方法。
这基本上意味着FENCE
指令实质上取代了先前由序列化cpuid
在内存排序方面提供的功能。 但是,如果lock
-prefixed指令提供了与cpuid
相同的屏障功能,那么这可能是之前建议的方式,因为这些通常比cpuid
快得多,后者通常需要200个或更多周期。 这意味着存在lock
前缀指令无法处理的场景(可能是弱序场景),以及使用cpuid
地方,以及现在建议将mfence
作为替换,暗示比lock
前缀指令更强的屏障语义。
但是,我们可以用不同的方式解释上面的一些内容:请注意,在围栏指令的上下文中,经常提到它们是确保排序的性能有效方式 。 因此,这些说明可能并非旨在提供额外的障碍,而只是提供更有效的障碍。
实际上,几个周期的sfence
比串行化指令要快得多,例如cpuid
或lock
前缀指令,通常是20个周期或更多。 在另一方面mfence
不是一般至少比在现代硬件锁定指令4,速度更快。 尽管如此,它在引入或未来某些设计时可能会更快,或者预计它会更快但但并没有成功。
所以我不能根据手册的这些部分做出一定的评估:我认为你可以做出合理的论证,可以用任何一种方式解释它。
我们可以进一步查看英特尔ISA指南中各种非临时存储指令的文档。 例如,在非临时存储movnti
的文档中,您会找到以下引用:
由于WC协议使用弱排序的内存一致性模型,如果多个处理器可能使用不同的内存类型来读/写目标内存位置,则应使用SFENCE或MFENCE指令实现的防护操作与MOVNTI指令一起使用。
关于“如果多个处理器可能使用不同的存储器类型来读/写目标存储器位置”的部分对我来说有点混乱。 我希望这可以说“在使用弱排序提示的指令之间强制执行排序”或类似的东西。 实际上,实际的存储器类型 (例如,由MTTR定义)可能在这里甚至不起作用:当使用弱有序指令时,排序问题可能仅在WB存储器中出现。
据报道, mfence
指令在现代CPU上基于Agner fog的指令时序需要33个周期(背靠背延迟),但据报道, lock cmpxchg
等更复杂的锁定指令只需要18个周期。
如果mfence
提供的屏障语义不比lock cmpxchg
强,后者正在严格执行更多的工作,并且没有明显的理由让mfence
花费更长的时间 。 当然你可以争辩说, lock cmpxchg
比mfence
更重要,因此可以获得更多优化。 由于所有锁定的指令都比mfence
,即使不经常使用的指令,这个论点也会被削弱。 此外,您可以想象如果所有lock
指令共享一个屏障实现,则mfence
将使用相同的那个,因为它是最简单且最容易验证的。
因此,在我看来, mfence
的较慢表现是证据表明mfence
正在做一些额外的事情。
0.5这不是一个不透水的论点。 有些东西可能出现在勘误表中,显然是“按设计”而不是错误,例如popcnt
错误依赖于目标寄存器 - 因此一些勘误表可以被视为一种更新期望的文档形式,而不是总是暗示硬件错误。
1显然, lock
-prefixed指令还执行原子操作,这是不可能仅使用mfence
实现的,因此lock
前缀的指令肯定具有附加功能。 因此,为了使mfence
有用,我们希望它在某些场景中具有额外的屏障语义, 或者表现更好。
2他完全有可能阅读散文不同的手册的不同版本。
3 SFENCE
在SSE, lfence
和mfence
在SSE2。
4通常它的速度较慢:Agner在最近的硬件上列出了33个周期的延迟,而锁定指令通常约为20个周期。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.