不,mfence 没有在指令流上序列化,lfence(也就是)不会刷新存储缓冲区。
(在 Skylake 的实践中,mfence 确实阻止了后续 ALU 指令的乱序执行,而不仅仅是加载。(Proof: experiment details at the bottom of this answer)。因此它被实现为执行屏障,即使在纸面上它不需要是一个。但lock xchg 不是,而且也是一个完整的障碍。)
我建议阅读 Jeff Preshing 的 Memory Barriers Are Like Source Control Operations 文章,以更好地了解内存屏障需要做什么以及它们不需要做什么。它们通常不会(不需要)阻止乱序执行。
内存屏障限制了内存操作可以全局可见的顺序,不是(必然)指令执行的顺序。阅读@BeeOnRope 对您的更新的回答再次上一个问题:Does an x86 CPU reorder instructions? 了解更多关于如何在没有 OoO exec 的情况下发生内存重新排序,以及如何在没有内存重新排序的情况下发生 OoO exec。
停止管道和刷新缓冲区是实现屏障的一种(低性能)方法,used on some ARM chips,但是具有更多内存排序跟踪的高性能 CPU 可以具有更便宜的内存屏障,只有限制内存操作的顺序,而不是所有指令。对于内存操作,它们控制对 L1d 缓存的访问顺序(在存储缓冲区的另一端),不一定是存储将数据写入存储缓冲区的顺序。
x86 已经需要大量内存顺序跟踪来进行正常加载/存储以实现高性能,同时保持其强排序内存模型,其中仅允许StoreLoad reordering 对内核外部的观察者可见(即存储可以被缓冲直到稍后加载后)。 (英特尔的优化手册使用术语“内存顺序缓冲区”或 MOB,而不是存储缓冲区,因为它还必须跟踪加载顺序。如果事实证明推测性加载也占用了数据,它必须执行内存排序机器清除早期。)现代 x86 CPU 保留了尊重内存模型的错觉,同时实际执行无序的加载和存储。
mfence 只需将标记写入内存顺序缓冲区即可完成其工作,而不会成为后面 ALU 指令乱序执行的障碍。此标记至少必须防止以后的加载执行,直到mfence 标记到达存储缓冲区的末尾。 (以及在弱排序 WC 内存上排序 NT 存储和操作)。
(但同样,更简单的行为是一种有效的实现选择,例如,在mfence 之后,不让任何存储将数据写入存储缓冲区,直到所有早期加载都已退出并且早期存储已提交到 L1d 缓存。即完全耗尽MOB / 存储缓冲区。我不确切知道当前的 Intel 或 AMD CPU 是做什么的。)
特别是在 Skylake 上,my testing shows mfence 是前端(融合域)的 4 个微指令,以及在执行端口上实际执行的 2 个微指令(一个用于端口 2/3(加载/存储地址),以及一个用于端口 4(存储数据))。大概它是一种特殊的uop,它将标记写入内存顺序缓冲区。不需要执行单元的 2 个微指令可能类似于 lfence。我不确定他们是否会阻止前端甚至发出稍后的加载,但希望不会因为这会阻止以后执行独立的 ALU 操作。
lfence 是一个有趣的案例:除了作为 LoadLoad + LoadStore 屏障(即使对于弱排序加载;正常加载/存储已经排序),lfence 也是一个弱 执行障碍(注意mfence不是,只是lfence)。在所有早期指令“在本地完成”之前,它无法执行。大概这意味着从无序的核心“退休”。
但是一个 store 不能提交到 L1d 缓存直到 之后 它无论如何都退休了(即在它被认为是非投机性的之后),所以等待 store 从 ROB 退休(ReOrder Buffer for uops) 与等待存储缓冲区清空不同。见Why is (or isn't?) SFENCE + LFENCE equivalent to MFENCE?。
所以是的,CPU 管道确实必须在执行之前“通知”lfence,大概是在问题/重命名阶段。我的理解是lfence 在 ROB 为空之前无法发布。 (在 Intel CPU 上,lfence 是前端的 2 微指令,但根据 Agner Fog 的测试,它们都不需要执行单元。http://agner.org/optimize/。)
lfence 在 AMD Bulldozer 系列上更便宜:1 uop,每时钟 4 个吞吐量。 IIRC,它不会在这些 CPU 上进行部分序列化,因此您只能使用 lfence; rdtsc 阻止 rdtsc 在 Intel CPU 上提前采样时钟。
对于像cpuid 或iret 这样的完全序列化指令,它也会等到存储缓冲区耗尽。 (They're full memory barriers, as strong as mfence)。或类似的东西;它们是多个微指令,所以也许只有 last 一个进行序列化,我不确定cpuid 的实际工作发生在障碍的哪一侧(或者它是否不能与较早或较晚的说明)。无论如何,管道本身必须注意序列化指令,但完整的内存屏障效应可能来自执行 mfence 所做的微指令。
额外阅读:
在 AMD Bulldozer 系列上,sfence 与 mfence 一样昂贵,并且可能是一个强大的障碍。 (x86 文档为每种障碍的强度设置了最小值;它们不会阻止它们变得更强大,因为这不是正确性问题)。 Ryzen 不同:sfence 每 20c 吞吐量一个,而mfence 每 70c 1 个。
sfence 在 Intel 上非常便宜(一个 uop 用于 port2/port3,一个 uop 用于 port4),并且只需订购 NT 商店 wrt。正常存储,不刷新存储缓冲区或序列化执行。它可以每 6 个周期执行一次。
sfence 在退休之前不会耗尽存储缓冲区。它本身不会成为全局可见的,直到所有前面的存储首先成为全局可见,但 这与存储缓冲区的执行管道。存储缓冲区总是试图耗尽自己(即提交存储到 L1d)所以sfence 不需要做任何特殊的事情,除了在 MOB 中放置一种特殊的标记来阻止 NT 存储重新排序过去,不像正规商店的标记,仅用于订购。常规存储和稍后加载。
它的读取速度比执行速度快,因此它可以看到一个显示即将执行的指令的窗口。
请参阅this answer I wrote,这是我评论的更详细版本。它介绍了现代 x86 CPU 如何通过查看尚未执行的指令来发现和利用指令级并行性的一些基础知识。
在具有高 ILP 的代码中,最近的 Intel CPU 实际上很容易在前端出现瓶颈;后端有如此多的执行单元,除非存在数据依赖性或缓存未命中,或者您使用大量只能在有限端口上运行的单条指令,否则它很少成为瓶颈。 (例如矢量洗牌)。但是任何时候后端跟不上前端,乱序窗口就会开始填充指令以找到并行性。