CPU 中的内存排序

Lobsters Hottest 新闻

摘要

本文解释了CPU乐观地实现内存排序,并依赖回滚机制来处理竞争,同时澄清了关于强排序与弱排序架构的常见误解。

<p><a href="https://lobste.rs/s/idtlvo/memory_ordering_cpus">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/26 15:28

# CPU中的内存排序 来源:https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/ 我经常看到关于强排序架构(如x86或SPARC)与弱排序架构(如ARM或RISC-V)之间差异的论述,并有人断言弱排序机器本质上比强排序机器更具可扩展性。 关键的误解在于隐含的假设——各类CPU实际上都会严格遵守其内存模型进行每次内存访问。 但它们显然并非如此。 它们只是承诺*表现得仿佛*遵守了模型。在这看似微小的区别中,实则存在天壤之别。 需要明确的是,某些CPU核心确实严格遵守架构内存排序规则。但这种行为通常仅限于没有缓存的小型核心或微控制器。 几乎所有其他设计(包括更大的顺序执行设计!)都会采取某种捷径。具体来说,它们通常以乐观方式实现内存排序(https://en.wikipedia.org/wiki/Optimistic_concurrency_control)。其假设是大多数加载操作访问的数据最近未被其他核心修改(也没有正在进行的修改),且大多数存储操作不存在竞争。1 (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#10db3193-c76e-472b-b14f-c9c02db68f29) 如果所有加载的数据自指令首次进入流水线到提交期间都未改变,则可以任意顺序执行它们(乱序CPU正是如此操作)。如果存储操作不存在竞争(即没有两个CPU试图同时写入同一缓存行),且无人将读取这些结果,那么它们同样可以被自由重排。这正是大多数CPU在多数情况下所做的。 当然,有时这些排序规则会产生影响,否则我们根本不会定义它们。但这只在访问*存在竞争*时才重要:即当我们实际执行内存访问与该指令提交(指令在流水线后期变为“正式”状态,其更改对外部可见的时刻)之间,其他实体修改了我们感兴趣的内存(或至少包含该内存的缓存行)。在提交前,如果发现问题可以回滚指令,而竞争就是这类问题之一(与异常/陷阱、中断和分支预测错误等情况类似)。 因此,实际实现更像是一种“信任,但验证”的方法:我们假设几乎所有内存访问在绝大多数时候都没有竞争,并据此控制指令的执行方式。然而,我们会保留足够的元数据以在事后发现潜在的内存排序违规。2 (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#eadb160a-c643-48f4-964d-454878e51934)当检测到违规时,违规指令无法提交,程序状态会回滚到执行这些指令之前的状态,然后重新尝试执行。 *所有人*——无论弱排序还是强排序——都是这样实现的。3 (https://fgiesen.wordpress.com/2012/08/25/memory-ordering-in-cpus/#8c82bc36-7e2f-40d3-b43a-b946a7b170af)那么,内存模型之间的区别并不在于强内存模型机器按照内存模型规则执行每次内存访问,而弱内存模型机器则不然。真正的区别在于,*当出现竞争时*(即对我们而言,另一个实体修改了正在进行的指令已访问的内存),强排序机器比弱排序机器更可能报告冲突(并重试)。 此外,竞争在任何情况下都是缓慢的路径。弱排序机器在这方面确实有一些优势,但在实践中这往往不会带来太大好处(或者至少我从未在实际工作负载中看到显著优势)。我个人对于多线程代码的信条是*减少竞争,而非加快竞争处理*。你的经验可能有所不同。 强排序与弱排序机器的主要区别最终并不在于前者严格按序执行所有内存操作而后者不是,而在于前者需要为每个进行中的内存操作保留足够的元数据以检查是否存在排序违规,而后者主要处理仅需通过内存屏障进行排序的宽松加载/存储操作。这也意味着在出现竞争时,弱排序机器有更多合法的内存访问顺序(因此可以完成退役)。 这些因素确实约束了实现方式,像x86 TSO这样的内存模型并非零成本,但实际情况远比“x86和SPARC必须按序执行所有内存操作,而ARM和RISC-V CPU则不需要”要微妙得多。存在成本,但衡量起来并不直接。4 (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#320ecacb-31dd-4a74-bd34-de2dbf46b1e0) 此外,作为一个实证数据点,我们现在有配备数百个CPU的服务器系统,既有弱排序的(主要是ARM),也有强排序的(主要是x86)。这两类系统大致都表现出相同的特性:在“无共享”型工作负载上表现良好,倾向于使用难以优化配置的NUMA架构,而实际的竞争瓶颈会彻底破坏你的工作日。总体而言,从处理拥有大量核心的机器中得到的主要收获是,正如以往,技术前沿是痛苦之地,如果选择每个插槽CPU核心数约为当前市售最高配置一半的系统,你会获得更好的体验。 这并非说没有差异。差异显然存在。但围绕该话题的论述暗示,在众核CPU的高负载多线程工作负载中,采用弱内存模型的CPU应表现更好(或至少能效更高),而我的经验并非如此。这更像是不同厂商CPU微架构之间的差异——各有优劣,而非一种方案可扩展而另一种不可扩展的鲜明对比。 ### 脚注 1. 来自同一核心的加载/存储操作的重排序在乱序CPU上也是一个关注点,而且实际上是主要问题。乱序设计中的一个关键复杂性是,即使同一硬件线程执行的指令并非按程序顺序执行,也要假装它们按程序顺序执行。然而,这在所有乱序设计中都是如此,且完全独立于CPU内存模型——内存模型关注的是多个实体间的事件排序,而非源自单个实体的事件流如何产生。↩︎ (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#10db3193-c76e-472b-b14f-c9c02db68f29-link) 2. 我写的是“潜在的”排序违规,因为这种跟踪通常是保守的,体现在多个方面。它必须正确标记所有实际竞争的访问对,但即使将一些非竞争访问也标记为潜在违规,也不会损害正确性。如果这种情况发生太频繁会降低性能,但设计者可以根据目标权衡调整这一设置。↩︎ (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#eadb160a-c643-48f4-964d-454878e51934-link) 3. 再次说明,根据机器类型和内存类型等因素会有一些区别。没有缓存的顺序执行机器(或在有缓存的机器上使用未缓存内存访问)确实严格遵守内存模型,这确实很慢,但这些场景的目标并非速度。有缓存的顺序执行机器通常会在这方面进行一些温和的推测,至少是为了更早地将独占缓存行保留操作放入流水线。而在乱序机器上,激进的指令重排和推测是基本要求。↩︎ (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#8c82bc36-7e2f-40d3-b43a-b946a7b170af-link) 4. 某些CPU(如Apple Silicon)可以选择性地启用硬件TSO模式。这提供了一个TSO对性能影响的数值(常被引用为约7%),但需带一个重大注释,即“在该特定实现中TSO的性能影响”。Apple Silicon仍然是ARM CPU核心,设计用于运行AArch64代码,并(推测)针对AArch64跟踪进行了优化,其中大多数加载/存储操作是宽松的。没有理由假设Apple Silicon核心的设计是为了实现最佳的TSO性能。它们首先是ARM核心,所使用的算法和内部数据结构将针对典型的ARM工作负载进行调整。这些CPU中的硬件TSO旨在辅助Rosetta(即x86模拟)。它比在软件中模拟TSO(如FEX (https://fex-emu.com/) 等模拟器在缺乏硬件TSO时必须做的)要快得多(且推测能效也更高)。但它仍然是一种兼容特性,并非CPU设计的主要运行模式。实际的x86处理器会做出不同的实现选择和权衡。↩︎ (https://fgiesen.wordpress.com/2026/08/25/memory-ordering-in-cpus/#320ecacb-31dd-4a74-bd34-de2dbf46b1e0-link)

相似文章

x86 模拟的难题

Hacker News Top

本文讨论在 ARM 的弱序内存模型上模拟 x86-TSO 内存模型的挑战,涵盖原子指令、分裂锁和未缓存内存等问题,并探讨潜在解决方案。

抢占是内存重排序的GC(2019)

Hacker News Top

一篇2019年的博客文章认为,抢占(中断)可以用作无锁编程中内存排序的预支付屏障,类似于垃圾回收是一种沉没成本,并展示了在Linux/x86上实现事件计数和非对称标志翻转的实现。

平坦内存与分段内存:递归本质

Hacker News Top

本文讨论了x86内存分段的演变,受Unix平坦内存模型影响,并解释CHERI和WebAssembly如何为安全和安保重新引入分段方法,强调了内存细分的递归性质。

内存的物理原理(即 JavaScript ECS 能行吗?)

Lobsters Hottest

本文介绍了一份详细的基准测试,比较了 JavaScript 中用于二维物理模拟的 ECS 和 OOP 架构,测试了内存局部性和多维性能,包括 broad-phase 算法和排序策略,结果基于 M4 Mac。