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

问题描述 投票:7回答:1

我想知道的是,如果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几乎正在刷写写缓冲区,或者至少延伸到写缓冲区和其他内核以确保我未来的加载/存储是最新的。

当基准标记时,两个指令都需要约100个循环才能完成。所以我无论如何都看不出那么大的差异。

主要是我只是困惑。我的指令基于互斥体中使用的lock,但是这些包含没有内存栅栏。然后我看到使用内存防护但没有锁的无锁编程。我知道AMD64有一个非常强大的内存模型,但过时的值可以在缓存中持续存在。如果lock的行为与mfence的行为不同,那么互斥量如何帮助您查看最新值?

multithreading assembly x86 cpu-architecture memory-barriers
1个回答
5
投票

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

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

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

来自WC内存的MOVNTDQA可能会通过更早的锁定指令

问题:从WC(写入组合)存储器加载的(V)MOVNTDQA(流加载指令)的执行可能看起来通过访问不同高速缓存行的较早锁定指令。

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

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

由此,我们可以确定(1)英特尔可能打算从WC类型的内存中锁定NT加载的锁定指令,否则这不是errata0.5和(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 one)出现在2016年1月,差不多两年之前SKL155(locked one)出现在2017年底,或者为什么后者在完全相同的Haswell勘误之后出现了这么多。

人们可能会猜测英特尔将来会做些什么。由于他们无法/愿意通过Skylake更改Haswell的lock指令,代表数亿(数十亿?)已部署的芯片,他们永远无法保证锁定的指令可以阻挡NT负载,因此他们可能会考虑制作这是未来记录的架构行为。或者他们可能会更新锁定的指令,因此他们确实对这些读取进行了限制,但实际上你可能不会依赖这十年或更长时间,直到具有当前非击剑行为的筹码几乎没有流通。

类似于Haswell,根据BV116BJ138,NT载荷可能分别通过Sandy Bridge和Ivy Bridge的早期锁定指令。早期的微体系结构也可能会遇到这个问题。在Skylake之后,Broadwell和微体系结构中似乎不存在这个“错误”。

Peter Cordes在mfence结束时写了一些关于Skylake this answer变化的文章。

在我知道勘误表之前,这个答案的剩余部分是我的原始答案,而这主要是出于历史兴趣。

Old Answer

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

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

Details

文档

mfence的记忆一致性效果与lock前缀指令(包括xchg与内存操作数,隐式锁定)提供的程度不完全清楚。

我认为可以肯定地说,仅仅关于回写存储器区域而不涉及任何非时间访问,mfence提供与lock-prefixed操作相同的排序语义。

可以讨论的是mfence在上述情况之外是否与lock前缀指令完全不同,特别是当访问涉及WB区域以外的区域或涉及非时间(流)操作时。

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

例如,在this thread引用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指令的部分,它明确提到弱内存区域:“SFENCE,LFENCE和MFENCE指令**提供了一种性能有效的方法来确保在生成弱有序结果和例程的例程之间加载和存储内存排序消耗这些数据。“

我们是否在这些行之间进行了阅读,并认为这些是完成此操作的唯一指令,并且前面提到的技术(包括锁定指令)对弱内存区域没有帮助?我们可以通过注意围栅指令与弱序非临时存储指令同时引入3以及11.6.13缓存性提示指令中专门针对弱有序指令处理的文本来找到对此思想的一些支持:

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

同样,这里特别提到围栅指令适用于屏蔽弱有序指令。

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

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

这基本上意味着FENCE指令基本上取代了以前在内存排序方面序列化cpuid所提供的功能。然而,如果lock前缀指令提供与cpuid相同的屏障能力,那可能是之前建议的方式,因为这些通常比cpuid快得多,后者通常需要200个或更多周期。这意味着存在lock前缀指令无法处理的场景(可能是弱序场景),以及使用cpuid的地方,以及mfence现在被建议作为替代,暗示比lock前缀指令更强的屏障语义。

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

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

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

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

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

关于“如果多个处理器可能使用不同的存储器类型来读/写目标存储器位置”的部分对我来说有点混乱。我希望这可以说“在使用弱排序提示的指令之间强制执行排序”或类似的东西。实际上,实际的存储器类型(例如,如MTTR所定义的)可能在这里甚至不起作用:当使用弱有序指令时,排序问题可能仅在WB存储器中出现。

性能

据报道,mfence指令在现代CPU上基于Agner fog的指令时序需要33个周期(背对背延迟),但据报道,像lock cmpxchg这样的更复杂的锁定指令只需要18个周期。

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

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


0.5这不是一个不透水的论点。有些东西可能出现在勘误表中,显然是“按设计”而不是错误,例如popcnt错误依赖目标寄存器 - 因此一些勘误表可以被视为一种更新期望的文档形式,而不是总是暗示硬件错误。

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

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

SSE中的3个SFENCE,SSE2中的lfencemfence

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

© www.soinside.com 2019 - 2024. All rights reserved.