首页 文章

lock xchg与mfence具有相同的行为吗?

提问于
浏览
7

我想知道的是,如果 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 回答

  • 5

    我相信你的问题与询问 mfence 是否与x86上的 lock -prefixed指令具有相同的屏障语义,或者在某些情况下它是否提供更少1或额外保证相同 .

    我目前最好的答案是,这是英特尔的意图,并且ISA文档保证 mfencelock ed指令提供相同的防护语义,但由于实现疏忽, mfence 实际上在最近的硬件上提供了更强的防护语义(至少从Haswell开始) . 特别是, mfence 可以阻止来自WC类型存储器区域的后续非临时负载,而 lock ed指令则不会 .

    我们知道这一点,因为英特尔在处理器勘误中告诉我们这一点,例如HSD162 (Haswell)SKL155 (Skylake),它告诉我们锁定的指令不会阻止从WC内存的后续非临时读取:

    来自WC存储器的MOVNTDQA可以传递早期锁定指令问题:从WC(写入组合)存储器加载的(V)MOVNTDQA(流加载指令)的执行可能看起来传递先前锁定的访问不同高速缓存行的指令 . 含义:期望锁定后续(V)MOVNTDQA指令的软件可能无法正常运行 . 解决方法:未确定 . 依赖于锁定指令来阻止后续执行(V)MOVNTDQA的软件应在锁定指令和后续(V)MOVNTDQA指令之间插入MFENCE指令 .

    由此,我们可以确定(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类似,根据BV116BJ138,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区域以外的区域或涉及非时间(流)操作时 .

    例如,您可以找到一些建议(例如herehere),当涉及WC类型的操作(例如,NT存储)时, mfence 暗示强屏障语义 .

    例如,在_2906941中引用McCalpin博士(重点补充):

    围栏指令只需要绝对确保所有非临时存储在随后的“普通”存储之前是可见的 . 最重要的情况是并行代码,其中并行区域末端的“屏障”可能包括“普通”存储 . 如果没有围栅,处理器可能仍然在写入组合缓冲区中修改了数据,但是通过屏障并允许其他处理器读取写入组合数据的“陈旧”副本 . 此方案也可能适用于操作系统从一个核心迁移到另一个核心的单个线程(不确定此情况) . 我不记得详细的推理(今天早上咖啡还不够),但是你想要在非临时商店之后使用的指令是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 指令的部分,它明确提到弱内存区域:"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 中专门处理弱有序指令的文本来找到对此思想的一些支持:

    数据消费者知道数据被弱排序的程度可能因这些情况而异 . 因此,应使用SFENCE或MFENCE指令来确保生成弱排序数据的例程和使用数据的例程之间的排序 . SFENCE和MFENCE通过保证程序顺序中SFENCE / MFENCE之前的每个商店指令在跟随围栏的商店指令之前是全局可见的,提供了一种性能有效的方式来确保订购 .

    同样,这里的围栏说明是具体的提到适合于弱有序的指令 .

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

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

    这基本上意味着 FENCE 指令实质上取代了先前在内存排序方面序列化 cpuid 提供的功能 . 但是,如果 lock -prefixed指令提供了与 cpuid 相同的屏障功能,那么这可能是之前建议的方式,因为它们通常比 cpuid 快得多,后者通常需要200个或更多个周期 . 这意味着有些场景(可能是弱排序的场景) lock -prefixed指令没有处理,并且 cpuid 正被使用,并且 mfence 现在被建议作为替换,暗示比 lock -prefixed指令更强的屏障语义 .

    但是,我们可以用不同的方式解释上面的一些内容:请注意,在围栏指令的上下文中,经常提到它们是确保排序的性能有效方式 . 因此,这些说明可能并非旨在提供额外的障碍,而只是提供更有效的障碍 .

    实际上,在几个周期内的 sfence 比串行化指令要快得多,例如通常为20个周期或更长的 cpuidlock -前缀指令 . 另一方面, mfence 通常不比锁定指令快4,至少在现代硬件上是这样 . 尽管如此,它在引入或未来某些设计时可能会更快,或者预计它会更快但但并没有成功 .

    所以我不能根据手册的这些部分做出一定的评估:我认为你可以做出合理的论证,可以用任何一种方式解释它 .

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

    由于WC协议使用弱排序的内存一致性模型,如果多个处理器可能使用不同的内存类型来读取/写入目标内存位置,则应使用SFENCE或MFENCE指令实现的防护操作与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 cmpxchgmfence 更重要,因此可以获得更多优化 . 由于所有锁定的指令都比 mfence 快得多,甚至不经常使用的指令,因此这一论点被削弱了 . 此外,您可以想象如果所有 lock 指令共享一个屏障实现, mfence 将只使用相同的那个,因为它是最简单和最容易验证的 .

    因此,在我看来, mfence 的较慢表现是 mfence 做了一些额外的重要证据 .


    0.5这不是一个不透水的论点 . 有些东西可能会出现在勘误表中,显然是"by design"而不是错误,例如 popcnt 对目标寄存器的错误依赖 - 所以一些勘误表可以被认为是一种更新期望的文档形式,而不是总是暗示硬件错误 .

    1显然, lock -prefixed指令也执行原子操作,这是不可能单独用 mfence 实现的,因此 lock -prefixed指令肯定具有附加功能 . 因此,对于 mfence 有用,我们会期望它在某些情况下要么具有额外的屏障语义,要么表现得更好 .

    2他完全有可能阅读散文不同的手册的不同版本 .

    SSE中为 SFENCE ,SSE2中为 lfencemfence .

    4通常它的速度较慢:Agner在最近的硬件上列出了33个周期的延迟,而锁定指令通常约为20个周期 .

相关问题