0.5 sup> 和 (2) 锁定指令不会实际上这样做,英特尔无法或选择不通过微码更新来解决此问题,建议使用 mfence。
在 Skylake 中,mfence 实际上失去了针对 NT 负载的额外防护功能,根据 SKL079:来自 WC 内存的 MOVNTDQA 可能通过早期的 MFENCE 指令 - 这与文本几乎相同lock-指令勘误表,但适用于 mfence。但是,此勘误表的状态是“BIOS 可能包含此勘误表的解决方法。”,这通常是英特尔所说的“微码更新解决此问题”。
这一系列勘误表或许可以用时间来解释:Haswell 勘误表仅出现在 2016 年初,也就是该处理器发布几年后,因此我们可以假设该问题在此之前的一段时间内引起了英特尔的注意。在这一点上,Skylake 几乎可以肯定已经在野外了,显然是一个不那么保守的mfence 实现,它也没有在 WC 类型的内存区域上隔离 NT 负载。基于锁定指令的广泛使用,修复锁定指令一直工作到 Haswell 的方式可能是不可能的或昂贵的,但需要某种方法来隔离 NT 负载。 mfence 显然已经在 Haswell 上完成了这项工作,Skylake 将得到修复,以便 mfence 也在那里工作。
这并不能真正解释为什么 SKL079(mfence 一个)出现在 2016 年 1 月,比 SKL155(locked 一个)在 2017 年末出现早了近两年,或者为什么后者在相同之后出现了这么多然而,Haswell 勘误表。
人们可能会猜测英特尔将来会做什么。由于他们无法/不愿意通过 Skylake 更改 Haswell 的 lock 指令,代表数亿(数十亿?)个已部署的芯片,他们将永远无法保证锁定的指令会限制 NT 负载,因此他们可能考虑将其作为未来记录的、结构化的行为。或者他们可能会更新锁定的指令,所以他们会屏蔽这样的读取,但实际上你不能依赖这个可能十年或更长时间,直到具有当前非屏蔽行为的芯片几乎停止流通。
与 Haswell 类似,根据 BV116 和 BJ138,NT 加载可能会分别在 Sandy Bridge 和 Ivy Bridge 上传递较早的锁定指令。早期的微架构也可能会遇到这个问题。在 Skylake 之后的 Broadwell 和微架构中似乎不存在这个“错误”。
Peter Cordes 在 this answer 末尾写了一些关于 Skylake mfence 更改的文章。
这个答案的其余部分是我最初的答案,在我知道勘误表之前,主要是为了历史兴趣。
旧答案
我对答案的明智猜测是,mfence 提供了额外的屏障功能:在使用弱排序指令的访问之间(例如,NT 存储)以及可能在弱排序的区域访问之间(例如, WC 型内存)。
也就是说,这只是一个有根据的猜测,您可以在下面找到我的调查的详细信息。
详情
文档
目前尚不清楚mfence 的内存一致性效果与lock 前缀指令(包括带有隐式锁定的内存操作数的xchg)提供的内存一致性效果的不同程度。
我认为可以肯定地说,仅就回写内存区域而言,不涉及任何非临时访问,mfence 提供与lock-prefixed 操作相同的排序语义。
有待商榷的是mfence 是否与lock-prefixed 指令在涉及上述之外的场景时完全不同,特别是当访问涉及非 WB 区域或非时间(流)时涉及操作。
例如,您可以找到一些建议(例如here 或here),mfence 在涉及 WC 类型的操作(例如 NT 存储)时意味着强屏障语义。
例如,在 this thread 中引用 McCalpin 博士的话(强调):
栅栏指令只需要绝对确定所有
非临时存储在随后的“普通”之前可见
店铺。这很重要的最明显情况是平行的
代码,其中并行区域末尾的“障碍”可能包括
一家“普通”的商店。没有栅栏,处理器可能仍然有
写入组合缓冲区中的修改数据,但通过
屏障并允许其他处理器读取
写入组合数据。这种情况也可能适用于单个
操作系统从一个核心迁移到另一个核心的线程(不是
确定这个案例)。
我不记得详细的推理(咖啡还不够这个
早上),但是你想在非临时之后使用的指令
商店是一个MFENCE。 根据第 3 卷第 8.2.5 节
SWDM,MFENCE 是唯一可以防止两者的栅栏指令
后续加载和后续存储不会提前执行
围栏的完成。我很惊讶这不是
11.3.1 节中提到过,它告诉您它的重要性
使用写组合时手动确保连贯性,但不
告诉你怎么做!
让我们看看引用的英特尔 SDM 的第 8.2.5 节:
加强或削弱记忆排序模型
英特尔 64 和
IA-32 架构提供了几种机制来加强或
削弱内存排序模型以处理特殊编程
情况。这些机制包括:
• I/O 指令,锁定
指令、LOCK 前缀和序列化指令强制
处理器上的排序更强。
• SFENCE 指令
(介绍了奔腾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 指令。
与 McCalpin 博士的解释相反2,我认为这部分对于 mfence 是否做了额外的事情有点模棱两可。涉及 IO、锁定指令和序列化指令的三个部分确实意味着它们在操作之前和之后的内存操作之间提供了一个完整的屏障。对于弱序内存,它们没有任何例外,在 IO 指令的情况下,人们还会假设它们需要以与弱序内存区域一致的方式工作,因为弱序内存区域通常用于 IO。
然后是FENCE 指令部分,它明确提到了弱内存区域:“SFENCE、LFENCE 和 MFENCE 指令**提供了一种高效的方式来确保加载和存储内存在产生弱排序结果的例程和使用该数据的例程之间进行排序。”
我们是否从字里行间解读并认为这些是完成此任务的唯一指令,并且前面提到的技术(包括锁定指令)对薄弱的内存区域没有帮助?我们可以通过注意到栅栏指令与弱序非临时存储指令同时引入3 以及类似 11.6.13 中的文本来找到对这一想法的支持Cacheability Hint Instructions 专门处理弱序指令:
数据消费者知道数据弱的程度
对于这些情况,ordered 可能会有所不同。因此,SFENCE 或 MFENCE
指令应用于确保例程之间的顺序
生成弱序数据和消耗数据的例程。科学
和 MFENCE 提供了一种高效的方式来确保按顺序排序
保证 SFENCE/MFENCE 之前的每条存储指令
在存储指令之前,程序顺序是全局可见的
跟着栅栏走。
再次,这里特别提到了栅栏指令,以适用于弱序指令的栅栏。
我们还支持这样一种观点,即锁定指令可能不会在上面已经引用的最后一句话中的弱排序访问之间提供障碍:
请注意,SFENCE、
LFENCE 和 MFENCE 指令提供了一种更有效的方法
控制内存排序而不是 CPUID 指令。
这基本上意味着FENCE 指令实质上替换了之前序列化cpuid 在内存排序方面提供的功能。然而,如果lock-prefixed 指令提供与cpuid 相同的屏障能力,那可能是先前建议的方式,因为这些通常比cpuid 快得多,后者通常需要200 个或更多周期。这意味着存在lock-prefixed 指令无法处理的场景(可能是弱排序场景),在哪里使用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 更重要,因此得到了更多的优化。 all 的锁定指令比mfence 快得多,即使是不经常使用的指令也削弱了这一论点。此外,您可以想象,如果所有lock 指令共享一个屏障实现,mfence 将简单地使用相同的屏障实现,因为这是最简单和最容易验证的。
因此,在我看来,mfence 较慢的性能是mfence 正在做一些额外的重要证据。
0.5 这不是一个无懈可击的论点。有些东西可能出现在勘误表中,显然是“设计使然”而不是错误,例如popcnt 对目标寄存器的错误依赖——因此一些勘误表可以被视为一种更新预期的文档形式,而不是总是暗示硬件错误。
1 显然,lock-prefixed 指令也执行一个原子操作,这是不可能单独使用 mfence 实现的,所以 lock -前缀指令肯定有额外的功能。因此,为了使mfence 有用,我们希望它在某些场景中具有额外的屏障语义,或性能更好。
2也完全有可能他正在阅读不同版本的手册,其中散文不同。
3SSE 中的SFENCE、SSE2 中的lfence 和mfence。
4 而且通常速度较慢:Agner 列出了在最近的硬件上的延迟为 33 个周期,而锁定指令通常约为 20 个周期。