内存屏障是否既充当标记又充当指令?

Ste*_*eve 2 x86 assembly instruction-set cpu-architecture memory-barriers

我读过有关内存屏障如何工作的不同内容。

例如,用户Johan在这个问题中的回答说,内存屏障是 CPU 执行的指令。

虽然用户Peter Cordes在这个问题中的评论说了以下关于 CPU 如何重新排序指令的内容:

它的读取速度比执行速度快,因此它可以看到即将到来的指令的窗口。有关详细信息,请参阅 x86 标签 wiki 中的一些链接,例如 Agner Fog 的 microarch pdf,以及 David Kanter 对 Intel Haswell 设计的文章。当然,如果您只是用谷歌搜索“乱序执行”,您会找到您应该阅读的维基百科文章。

所以我根据上面的评论猜测,如果指令之间存在内存屏障,CPU将看到这个内存屏障,这导致CPU不会对指令重新排序,所以这意味着内存屏障是一个“标记”让CPU看到而不是执行。


现在我的猜测是,内存屏障既充当标记又充当 CPU 执行的指令。

对于标记部分,CPU 看到指令之间存在内存屏障,这导致 CPU 不会对指令进行重新排序。

至于指令部分,CPU会执行内存屏障指令,这会导致CPU做一些诸如刷新存储缓冲区之类的事情,然后CPU会继续执行内存屏障之后的指令。

我对么?

Pet*_*des 5

不,mfence不在指令流上进行序列化,并且lfence(即)不会刷新存储缓冲区。

(在 Skylake 上的实践中,mfence 确实会阻止后续 ALU 指令的无序执行,而不仅仅是加载。(证明:本答案底部的实验详细信息)。因此,它被实现为执行屏障,尽管在纸面上它不是必须是其中之一。但事实lock xchg并非如此,而且也是一个完整的障碍。)

我建议阅读 Jeff Preshing 的Memory Barriers Are Like Source Control Operations文章,以更好地了解内存屏障需要做什么以及不需要做什么。一般来说,它们不(不需要)阻止乱序执行。


内存屏障限制内存操作全局可见的顺序,而不是(必然)指令执行的顺序。 再次阅读@BeeOnRope 对您之前问题的更新答案:Does an x​​86 CPU reorder instructions? 详细了解如何在没有 OoO exec 的情况下发生内存重新排序,以及如何在没有内存重新排序的情况下发生 OoO exec。

停止管道和刷新缓冲区是实现屏障的一种(低性能)方法,在某些 ARM 芯片上使用,但具有更多内存排序跟踪功能的高性能 CPU 可以拥有更便宜的内存屏障,这些内存屏障仅限制内存操作的排序,而不是全部指示。对于内存操作,它们控制对 L1d 缓存(位于存储缓冲区的另一端)的访问顺序,而不一定是存储将数据写入存储缓冲区的顺序。

x86 已经需要大量内存顺序跟踪来进行正常加载/存储以获得高性能,同时维护其强排序内存模型,其中仅允许StoreLoad 重新排序对核心外部的观察者可见(即存储可以缓冲到稍后加载之后) 。(英特尔的优化手册使用术语“内存顺序缓冲区”或 MOB,而不是存储缓冲区,因为它还必须跟踪加载顺序。如果发现推测加载也占用了数据,则必须清除内存排序机早期。)现代 x86 CPU 保留了尊重内存模型的错觉,但实际上却无序地执行加载和存储。

mfence只需将标记写入内存顺序缓冲区即可完成其工作,而不会成为后续 ALU 指令的无序执行的障碍。该标记必须至少阻止以后的加载执行,直到mfence标记到达存储缓冲区的末尾。(以及对弱排序 WC 内存进行 NT 存储和操作的排序)。

(但同样,更简单的行为是一个有效的实现选择,例如,在将数据写入存储缓冲区后,不让任何存储,mfence直到所有早期加载都已退出并且早期存储已提交给 L1d 缓存。即完全耗尽 MOB / 存储缓冲区。我不知道当前的 Intel 或 AMD CPU 的具体功能。)


具体在 Skylake 上,我的测试显示 mfence前端(融合域)有 4 个微指令,实际在执行端口上执行的有 2 个微指令(一个用于端口 2/3(加载/存储地址),一个用于端口 4(存储地址))。数据))。据推测,它是一种特殊类型的微指令,它将标记写入内存顺序缓冲区。不需要执行单元的 2 个 uop 可能类似于lfence. 我不确定它们是否会阻止前端发出稍后的加载,但希望不会,因为这会阻止以后执行独立的 ALU 操作。


lfence是一个有趣的情况:除了是一个 LoadLoad + LoadStore 屏障(即使对于弱排序加载;正常加载/存储已经排序),lfence也是一个弱执行屏障(注意mfence不是,只是lfence)。在所有早期指令“本地完成”之前,它无法执行。据推测,这意味着从无序核心“退休”。

但是存储在无论如何退出之前都无法提交到 L1d 缓存(即在已知它是非推测性的之后),因此等待存储从 ROB(微指令的重新排序缓冲区)退出与等待并不是一回事使存储缓冲区清空。请参阅为什么(或不是?)SFENCE + LFENCE 等同于 MFENCE?

lfence所以是的,CPU 管道在执行之前确实必须“注意” ,大概是在发出/重命名阶段。我的理解是,lfence在 ROB 为空之前不能发出。(在 Intel CPU 上,lfence前端为 2 uops,但根据 Agner Fog 的测试,它们都不需要执行单元。http: //agner.org/optimize/。)

lfenceAMD Bulldozer 系列的价格甚至更便宜:1 uop,每时钟 4 个吞吐量。IIRC,它在这些 CPU 上不是部分序列化,因此您只能用于lfence; rdtsc停止rdtsc在 Intel CPU 上提前采样时钟。


对于完全序列化指令(如cpuidiret),它还会等到存储缓冲区耗尽。(它们是完整的记忆障碍,与 一样强mfence)。或类似的东西; 它们是多个微指令,所以可能只有最后一个进行序列化,我不确定实际工作cpuid发生在屏障的哪一侧(或者它是否不能与较早或较晚的指令重叠)。无论如何,管道本身必须注意序列化指令,但完整的内存屏障效应可能来自执行该操作的微指令mfence


额外阅读:

在 AMD Bulldozer 系列中,sfence与 一样昂贵mfence,并且可能是一样强大的障碍。(x86 文档为每种屏障的强度设置了最小值;它们不会阻止它们变得更强,因为这不是正确性问题)。Ryzen 则不同:sfence每 20c 吞吐量有 1 个,而mfence每 70c 吞吐量有 1 个。

sfence在 Intel 上非常便宜(一个用于 port2/port3 的 uop,一个用于 port4 的 uop),并且只需订购 NT 存储。正常存储,不刷新存储缓冲区或序列化执行。它可以每 6 个周期执行一次。

sfence在退出之前不会耗尽存储缓冲区。直到所有先前的存储首先变得全局可见时,它本身才会变得全局可见,但这通过存储缓冲区与执行管道解耦。存储缓冲区总是试图耗尽自身(即将存储提交到 L1d),因此sfence不需要做任何特殊的事情,除了在 MOB 中放置一种特殊类型的标记来阻止 NT 存储对其进行重新排序,这与正规商店只放订单。常规商店和后期装载。


它的读取速度比执行速度快,因此它可以看到即将到来的指令的窗口。

我写的这个答案,这是我的评论的更详细版本。它介绍了现代 x86 CPU 如何通过查看尚未执行的指令来发现和利用指令级并行性的一些基础知识。

在具有高 ILP 的代码中,最新的 Intel CPU 实际上很容易在前端出现瓶颈;后端有如此多的执行单元,除非存在数据依赖性或缓存未命中,或者您使用大量只能在有限端口上运行的单个指令,否则它很少成为瓶颈。(例如向量洗牌)。但只要后端跟不上前端,无序窗口就会开始填充寻找并行性的指令。