当前位置: 首页 > 知识库问答 >
问题:

锁xchg是否具有与mfence相同的行为?

史默
2023-03-14

我想知道的是,从一个线程访问一个被其他线程变异(随机)的内存位置的角度来看,锁xchg是否会有类似于mfence的行为。它能保证我得到最新的价值吗?之后的内存读/写指令数?

我困惑的原因是:

8.2.2“不能使用I/O指令、锁定指令或序列化指令对读取或写入进行重新排序。”

-英特尔64位开发人员手册第3卷

这是否适用于多个线程?

mfence状态:

对在MFENCE指令之前发出的所有“从内存加载”和“存储到内存”指令执行序列化操作。此序列化操作确保在MFENCE指令之后的任何加载或存储指令全局可见之前,按程序顺序在MFENCE指令之前的每个加载和存储指令全局可见。MFENCE指令是根据所有加载和存储指令、其他MFENCE指令、任何SFENCE和LFENCE指令以及任何序列化指令(如CPUID指令)排序的。

-英特尔64位开发人员手册第3A卷

这听起来像是一个更有力的保证。听起来,mfence几乎是在刷新写缓冲区,或者至少是访问写缓冲区和其他内核,以确保我未来的加载/存储是最新的。

当进行基准标记时,两个指令大约需要100个周期才能完成。所以我看不出有多大的不同。

首先,我只是感到困惑。I基于互斥锁中使用的锁的指令,但这些指令不包含内存限制。然后我看到了无锁编程,它使用内存Geofence,但没有锁。我知道AMD64有一个非常强大的内存模型,但过时的值可以保留在缓存中。如果锁的行为与mfence不同,那么互斥体如何帮助您查看最新的值?

共有1个答案

朱自明
2023-03-14

我相信您的问题与询问mford是否与x86上的lock-前缀指令具有相同的障碍语义学,或者它是否提供更少的1或在某些情况下提供额外的保证。

我目前最好的回答是,这是英特尔的意图,ISA文档保证mfence和lock指令提供相同的防护语义,但由于实施疏忽,mfence实际上在最近的硬件上提供了更强的防护语义(因为至少Haswell)。尤其是,mfence可以限制WC型内存区域的后续非暂时加载,而锁定指令则不能。

我们之所以知道这一点,是因为Intel在HSD162(Haswell)和SKL155(Skylake)等处理器勘误表中告诉我们,锁定的指令不会限制后续从WC内存进行的非时态读取:

WC内存中的MOVNTDQA可能会传递先前锁定的指令

问题:从WC(写组合)内存加载的(V)MOVNTDQA(流式加载指令)的执行可能会传递访问不同缓存线的早期锁定指令。

暗示:期望锁定后续(V)MOVNTDQA指令的软件可能无法正常运行。

解决方法:未确定。依赖锁定指令限制(V)MOVNTDQA后续执行的软件应在锁定指令和后续(V)MOVNTDQA指令之间插入MFENCE指令。

由此,我们可以确定(1)Intel可能打算将锁定指令GeofenceNT从WC类型内存加载,否则这不会是勘误表0.5和(2)锁定指令实际上不会这样做,并且Intel无法或选择不通过微码更新来修复此问题,建议改为mford

在Skylake中,根据SKL079:来自WC内存的MOVNTDQA可能会通过早期的MFENCE说明,mford实际上失去了其对NT负载的附加Geofence功能-这与lock指令勘误表的文本几乎相同,但适用于mford。但是,此勘误表的状态是“BIOS可以包含此勘误表的解决方法。”,这通常是英特尔的说法,即“微代码更新解决了这个问题”。

这种勘误表的顺序可能可以用时间来解释:哈斯韦尔勘误表只出现在2016年初,也就是该处理器发布后的几年,因此我们可以假设在这之前的一段时间内,这个问题引起了英特尔的注意。在这一点上,Skylake几乎可以肯定已经在野外了,它显然是一个不太保守的mfence实现,它也没有限制WC类型内存区域上的NT负载。修复Haswell的锁定指令的工作方式可能是不可能的,也可能是昂贵的,因为它们的广泛使用,但需要某种方式来限制NT负载<显然,哈斯韦尔已经完成了这项工作,而Skylake也将得到修复,因此mfence也在那里工作。

但这并不能真正解释为什么SKL079(即mfenceone)出现在2016年1月,比SKL155(即lockedone)出现在2017年末早了将近两年,或者为什么后者出现在相同的Haswell勘误表之后。

人们可能会猜测英特尔未来会做什么。因为他们无法/不愿意通过Skylake更改Haswell的锁指令,这意味着几亿(十亿?)在部署的芯片中,他们永远无法保证锁定的指令限制NT负载,因此他们可能会考虑在将来将其作为有文档记录的架构行为。或者,他们可能会更新锁定的指令,所以他们会限制这样的读取,但实际上,在具有当前非限制行为的芯片几乎停止流通之前,你可能无法在十年或更长的时间内依赖它。

与Haswell类似,根据BV116和BJ138,NT负载可能会分别在Sandy Bridge和Ivy Bridge上传递早期锁定的指令。早期的微架构也可能遇到这个问题。这个“错误”似乎不存在于Skylake之后的Broadwell和微架构中。

彼得·科尔德斯在回答的最后写了一点关于天湖的变化。

这个答案的其余部分是我的原始答案,在我知道勘误表之前,它主要是为了历史兴趣而留下的。

我对答案的猜测是,mfence提供了额外的屏障功能:在使用弱顺序指令的访问之间(例如NT存储),或者在访问弱顺序区域之间(例如WC型内存)。

也就是说,这只是一个有根据的猜测,你会在下面找到我调查的细节。

mford的内存一致性效果与lock-前缀指令(包括带有内存操作数的xchg,隐式锁定)。

我认为可以肯定地说,仅就写回内存区域而言,而不涉及任何非时态访问,mfence提供了与前缀操作相同的排序语义。

有争议的是,当涉及上述以外的场景时,尤其是当访问涉及WB区域以外的区域或涉及非时态(流)操作时,mfence是否与带前缀的指令完全不同。

例如,您可以找到一些建议(如此处或此处),当涉及WC类型的操作(例如NT存储)时,mfence意味着强屏障语义。

例如,在本文中引用McCalpin博士的话(重点补充):

fence指令只需绝对确保所有非时态存储在后续“普通”存储之前可见。最明显的情况是在并行代码中,并行区域末尾的“屏障”可能包括“普通”存储。如果没有Geofence,处理器可能仍会在写组合缓冲区中修改数据,但会通过屏障,允许其他处理器读取写组合数据的“过时”副本。这种情况也可能适用于操作系统从一个核心迁移到另一个核心的单个线程(不确定这种情况)。

我记不起详细的理由(今天早上还没有足够的咖啡),但你想在非临时存储之后使用的指令是一个MFENCE。根据SWDM第3卷第8.2.5节,MFENCE是唯一的Geofence指令,可防止在Geofence完成之前执行后续加载和后续存储。我很惊讶,第11.3.1节中没有提到这一点,它告诉您在使用写组合时手动确保一致性是多么重要,但没有告诉您如何做到这一点!

让我们查看英特尔SDM第8.2.5节:

加强或削弱内存排序模型

Intel 64和IA-32架构提供了几种机制来加强或削弱内存排序模型以处理特殊编程情况。这些机制包括:

•输入/输出指令、锁定指令、锁定前缀和串行化指令在处理器上强制执行更强的排序。

•SFENCE指令(引入奔腾III处理器中的IA-32体系结构)以及LFENCE和MFENCE指令(引入奔腾4处理器)为特定类型的内存操作提供内存排序和序列化功能。

这些机制可按如下方式使用:

总线上的内存映射设备和其他I/O设备通常对写入其I/O缓冲区的顺序很敏感。输入/输出指令可用于(输入和输出指令)对此类访问施加强写入顺序,如下所示。在执行I/O指令之前,处理器等待程序中所有以前的指令完成,并等待所有缓冲写操作排入内存。只有指令提取和页表遍历才能传递I/O指令。直到处理器确定输入/输出指令已完成,才开始执行后续指令。

多处理器系统中的同步机制可能依赖于强内存排序模型。这里,程序可以使用锁定指令,例如XCHG指令或锁定前缀,以确保以原子方式执行对内存的读-修改-写操作。锁定操作通常与输入/输出操作类似,因为它们等待所有之前的指令完成,并等待所有缓冲写入到内存中(请参阅第8.1.2节“总线锁定”)。

程序同步也可以通过串行化指令执行(见第8.3节)。这些指令通常用于关键过程或任务边界,以强制在跳转到新代码段或发生上下文切换之前完成所有以前的指令。与I/O和锁定指令一样,处理器在执行串行化指令之前,会等待所有以前的指令都已完成,并且所有缓冲写入都已排入内存。

SFENCE、LFENCE和MFENCE指令提供了一种性能高效的方法,可以确保在产生弱顺序结果的例程和使用该数据的例程之间加载和存储内存顺序。这些说明的功能如下:

•SFENCE-序列化程序指令流中SFENCE指令之前发生的所有存储(写入)操作,但不影响加载操作。

•LFENCE-序列化程序指令流中LFENCE指令之前发生的所有加载(读取)操作,但不影响存储操作。

•MFENCE-序列化程序指令流中MFENCE指令之前发生的所有存储和加载操作。

请注意,SFENCE、LFENCE和MFENCE指令提供了比CPUID指令更有效的控制内存排序的方法。

与McCalpin博士的解释相反,我认为这一节对于mfence是否做了额外的事情有些含糊不清。涉及IO、锁定指令和序列化指令的三个部分确实意味着它们在操作前后的内存操作之间提供了一个完整的屏障。它们对弱序内存没有任何例外,在IO指令的情况下,我们还可以假设它们需要以一致的方式与弱序内存区域一起工作,因为这样的内存区域通常用于IO。

然后,指令栏部分明确提到了弱内存区域:“SFENCE、LFENCE和MFENCE指令**提供了一种性能高效的方法,确保在产生弱顺序结果的例程和使用该数据的例程之间加载和存储内存顺序。”

我们是否在字里行间阅读,并认为这意味着这些是实现这一点的唯一指令,而前面提到的技术(包括锁定指令)对弱内存区域没有帮助?我们可以通过注意到fence指令与弱序非时态存储指令同时引入,以及11.6.13中专门处理弱序指令的可缓存性提示指令中的文本,找到对这一想法的一些支持:

对于这些情况,数据使用者知道数据弱有序的程度可能会有所不同。因此,应该使用SFENCE或MFENCE指令来确保产生弱有序数据的例程和使用数据的例程之间的排序。SFENCE和MFENCE提供了一种性能高效的方式来确保排序,方法是保证在程序顺序中在SFENCE/MFENCE之前的每个存储指令在跟随Geofence的存储指令之前都是全局可见的。

同样,这里特别提到的Geofence指令适用于弱有序的Geofence指令。

我们还发现有人支持这样一种观点,即锁定指令可能不会在上面引用的最后一句话中的弱顺序访问之间提供障碍:

请注意,SFENCE、LFENCE和MFENCE指令提供了比CPUID指令更有效的控制内存排序的方法

这里基本上意味着FENCE指令在内存排序方面基本上取代了以前由序列化cpuid提供的功能。但是,如果lock-前缀指令提供与cpuid相同的屏障功能,这可能是之前建议的方式,因为这些通常比cpuid快得多,后者通常需要200个或更多周期。这意味着有一些场景(可能是弱有序的场景)lock-前缀指令没有处理,以及cpuid被使用的地方,以及现在建议将mford作为替换,这意味着比lock-前缀指令更强的障碍语义学。

然而,我们可以用不同的方式来解释上面的一些内容:注意,在Geofence说明的上下文中,经常提到它们是确保排序的高效性能方式。因此,这些说明可能不是为了提供额外的屏障,而是为了提供更有效的屏障。

事实上,在几个周期内执行sfence比串行化指令(如cpuid)或带前缀的指令(通常为20个周期或更多)要快得多。另一方面,至少在现代硬件上,mfence通常不比锁定指令快。尽管如此,当它被引入时,或者在一些未来的设计中,它本可以更快,或者也许它被期望更快,但这并没有实现。

所以我不能根据手册的这些部分做出一定的评估:我认为你可以提出一个合理的论点,即它可以被两种方式解释。

我们可以在《英特尔ISA指南》中进一步查看各种非时态存储指令的文档。例如,在非临时存储的文档中,可以找到以下引用:

由于WC协议使用弱有序内存一致性模型,如果多个处理器可能使用不同的内存类型来读/写目标内存位置,则使用SFENCE或MFENCE指令实现的Geofence操作应与MOVNTI指令一起使用。

关于“如果多个处理器可能使用不同的内存类型来读取/写入目标内存位置”的部分让我有点困惑。我希望这更像是“使用弱有序提示在指令之间强制执行全局可见的写入顺序”之类的东西。事实上,实际的内存类型(例如,由MTTR定义)可能在这里甚至没有发挥作用:当使用弱有序指令时,排序问题可能只出现在WB内存中。

据报道,基于Agner fog的指令计时,现代CPU上的mfence指令需要33个周期(背靠背延迟),但更复杂的锁定指令(如锁定cmpxchg)只需要18个周期。

如果mford提供的障碍语义学不比lock cmpxchg强,后者做的工作严格来说更多,并且mford没有明显的理由花费更长的时间。当然,您可以争辩说lock cmpxchgmford更重要,因此获得了更多的优化。由于所有锁定指令都比mford快得多,即使是不常使用的指令,这一论点也被削弱了。此外,您可以想象,如果所有lock指令共享一个屏障实现,mford将简单地使用相同的实现,因为这是最简单和最容易验证的。

因此,在我看来,mford的较慢性能是mford正在做一些额外工作的重要证据。

0.5这不是一个无懈可击的论点。勘误表中可能会出现一些明显是“设计”而不是错误的东西,例如popcnt对目标寄存器的错误依赖-因此某些勘误表可以被视为一种更新期望的留档形式,而不是总是暗示硬件错误。

显然,带锁前缀的指令也执行原子操作,这是单用mfence无法实现的,因此带锁前缀的指令肯定具有附加功能。因此,为了使mfence有用,我们希望它在某些场景中具有额外的屏障语义,或者性能更好。

也完全有可能他在阅读不同版本的手册,其中的散文有所不同。

3SSE中的SFENCE,SSE2中的lfordmford

4而且通常它更慢:Agner在最近的硬件上将其列为33周期延迟,而锁定指令通常约为20周期。

 类似资料:
  • 问题内容: Python Decorators是与Java注释或诸如Spring AOP或Aspect J之类的相同或相似,还是从根本上不同? 问题答案: Python装饰器只是语法糖,用于将一个函数传递给另一个函数并用结果替换第一个函数: 是语法糖 Java批注本身仅存储元数据,您必须进行检查以添加行为。 Java AOP系统是建立在Java之上的巨大事物,装饰器只是语言语法,几乎没有附加语义,

  • 问题内容: 不幸的是,HTML中没有CDATA。 遗憾的是,因为它非常适合添加包含XML的注释,因此您不必转义<和>,例如: 但是,可以识别CDATA部分,然后将其转换为HTML。例如: 或者它可以使用比CDATA更简单的语法。因为是可扩展的,所以可能有人添加了此功能。也许已经将它埋在里面的某处…有人知道吗? 问题答案: 您可以使用JavaDoc的标签:

  • 问题内容: 。 我有下表: 我需要用计算所有行。可能与聚合有关吗? 现在,我按如下操作: 问题答案: 如果您只需要对1的行数进行计数,则可以执行以下操作: 如果要计算 每 行的行数,则需要使用:

  • 我试图找到解释这种差异的视频或文本,但没有找到任何能说服我的东西。我才刚刚开始,如果可能的话,我只想专注于IDE。我正在研究JScript(vue)框架,我已经在使用VSCode来实现这一点。 附言:原谅我的英语。我知道这很可怕 坦克

  • 问题内容: 如果是这样,它是否有效地弃用了该property? 问题答案: 这是来自各种答案的经过验证的信息的汇总。 这些CSS属性实际上都是唯一的。除了使元素不可见之外,它们还具有以下附加效果: 折叠 元素通常会占用的空间 响应 事件 (例如,单击,按键) 在参与 的TabOrder

  • 我希望所有子模块都继承父版本号,但子模块要求我继续对父版本号进行硬编码(ModuleA pom.xml示例): 如何将版本号放在父pom中,并让模块继承它,而不必在多个地方更新它呢?到目前为止,我已经尝试了${project.version}和${project.parent.version},但Maven似乎对此很不满意。也不能完全删除版本引用。 有人对此有很好的答案吗?