本章摘要:
Intel x86/x64 CPU 的内存一致性模型(Memory Consistency Model)本质上是 TSO(Total Store Order)模型,但在此基础上增加了部分优化和扩展。以下是详细分析:
1. TSO 模型的核心特征
定义:TSO 是一种强一致性模型,介于顺序一致性(Sequential Consistency, SC)和弱一致性之间。其核心规则包括:
1. 写操作(Store)的顺序性:同一核心的写操作对所有其他核心保持程序顺序(即写指令的发射顺序)。
2. 读操作(Load)的灵活性:读操作可以绕过本核心未提交的写操作(通过写缓冲实现),但必须遵守依赖关系。
3. 写-读重排序:允许本核心的后续读操作先于之前的写操作对其他核心可见(唯一允许的重排序)。
典型表现:
若核心 A 执行 Store X → Store Y → Load Z,其他核心观察到的顺序一定是 X → Y。
但核心 A 的 Load Z 可能先于 Store X 执行(读绕过写缓冲)。
2. Intel x86 对 TSO 的实现
基础兼容性:
Intel 官方文档(如 SDM Vol.3 §8.2)明确将 x86 内存模型描述为 TSO。
所有现代 Intel CPU(从 P6 架构到 Skylake、Ice Lake 等)均遵循 TSO 的核心规则。
关键机制:
写缓冲(Write Buffer):
写操作先进入写缓冲,异步写入缓存/内存。
导致本核心外的后续读操作可能依然看到旧值(直到写缓冲刷出)。
存储转发(Store Forwarding):
若读操作依赖写缓冲中的未提交写,CPU 会直接返回写缓冲中的数据(而非缓存),避免停顿。
原子操作与屏障:
原子指令(如 LOCK 前缀)和内存屏障(如 MFENCE)用于强化顺序。
扩展优化:
存储合并(Store Combining):合并同一 cache line 的多次写(如问题 1 所述)。
弱化跨核心观察顺序:某些场景下(如非临时存储 NT 指令),允许偏离严格 TSO。
3. 与 SC(顺序一致性)的区别
SC 更严格:要求所有核心看到的读写顺序完全一致,且无任何重排序。
TSO 的宽松点:仅允许写-读重排序(其他顺序如写-写、读-读、读-写仍严格保持)。
cpp 语言代码:
// 示例:TSO 允许的重排序
// 初始:X=0, Y=0
// 核心 A | 核心 B
X = 1; | while (Y == 0);
Y = 1; | print(X); // TSO 下可能输出 0(但 SC 不会)
4. 验证 TSO 的经典案例
IRIW(Independent Reads of Independent Writes)测试:
若两个核心分别写 X 和 Y,另两个核心分别读 Y 和 X,TSO 保证所有核心最终对 X 和 Y 的顺序达成一致。
StoreLoad 重排序:
如下代码在 TSO 下可能因写缓冲导致 Load 先于 Store 完成:
asm 语言代码:
mov [X], 1 ; Store
mov eax, [Y] ; Load(可能先执行)
5. TSO 的局限性及增强
弱内存模型的挑战:
ARM/POWER 等弱内存模型(Weak Memory Model)需要显式屏障,而 x86 的 TSO 对开发者更友好。
强化顺序的场景:
使用 MFENCE 或 LOCK 前缀可禁止写缓冲优化,强制 StoreLoad 顺序。
原子操作(如 CMPXCHG)隐含屏障语义。
6. 现代扩展:从 TSO 到 x86-TSO
x86-TSO 的学术定义:
在 TSO 基础上,明确规范了原子操作、锁前缀、非临时存储等行为的语义。
实际差异:
某些场景(如 NT 存储、WC 内存类型)可能弱于标准 TSO。
综上所述:
Intel x86 CPU 默认采用 TSO 内存模型,其核心特征是保持写顺序、允许写-读重排序,并通过写缓冲和存储转发优化性能。开发者可依赖这一强模型简化编程,但在需要严格顺序时(如无锁编程)仍需谨慎使用屏障或原子指令。
______________________________
______________________________
正文:
CHAPTER 4 Total Store Order and the x86 Memory Model
一种广泛实现的内存一致性模型是总存储序(TSO)。TSO 最早由 SPARC 引入,更重要的是,它似乎与广泛使用的 x86 架构的内存一致性模型相匹配。RISC-V 也支持 TSO 扩展 RVTSO,部分原因是为了帮助移植最初为 x86 或 SPARC 架构编写的代码。本章采用与上一章关于顺序一致性类似的模式来介绍这个重要的内存一致性模型。我们首先阐述 TSO/x86 的动机(4.1 节),部分通过指出顺序一致性(SC)的局限性。然后在直观层面上介绍 TSO/x86(4.2 节),再进行更形式化的描述(4.3 节),解释系统如何实现 TSO/x86,包括原子指令和用于强制指令间顺序的指令(4.4 节)。最后,我们讨论学习 TSO/x86 的其他资源(4.5 节)并比较 TSO/x86 和 SC(4.6 节)。
4.1 TSO/x86 的动机【本节聚焦SC及其带来的问题】
处理器核心长期以来使用写(存储)缓冲区来保存已提交(退役)的存储操作,直到内存系统的其他部分能够处理这些存储。当存储操作提交时,数据进入写缓冲区;当要写入的块以读写一致性状态【注,相对于多读者状态;写,一定是单读写】进入缓存时,存储操作从写缓冲区退出。值得注意的是,存储操作可以在缓存获得对要写入块的读写一致性权限之前进入写缓冲区【注,这显然还未进入缓存】;因此,写缓冲区隐藏了处理存储缺失的延迟。由于存储操作很常见,能够避免在大多数存储操作上停顿是一个重要的优势。此外,不让核心停顿似乎是合理的,因为核心不需要任何东西,存储操作只是为了更新内存而不是核心状态。
对于单核处理器,可以通过确保对地址 A 的加载操作返回最近一次对 A 的存储值(即使有一个或多个对 A 的存储操作仍在写缓冲区中),使写缓冲区在架构上不可见。这通常通过以下两种方式之一实现:将最近一次对 A 的存储值旁路到对 A 的加载操作(其中 "最近一次" 由程序顺序确定),或者当对 A 的存储操作在写缓冲区中时,暂停对 A 的加载操作。
在构建多核处理器时,很自然地会使用多个核心,每个核心都有自己的旁路写缓冲区,并假设这些写缓冲区在架构上仍然不可见【注,会有什么问题么?】。

这个假设【这个假设是指:并假设这些写缓冲区在架构上仍然不可见。下面举反例,证明这么简单地在多核中架设写缓冲区,写缓冲区不可能总是不可见】是错误的。考虑表 4.1 中的示例代码(与上一章的表 3.3 相同)。假设有一个采用顺序执行核心的多核处理器,每个核心有一个单条目写缓冲区,并按以下顺序执行代码。
- 核心 C1 执行存储操作 S1,但将新存储的 NEW 值缓冲在其写缓冲区中。
- 同样,核心 C2 执行存储操作 S2,并将新存储的 NEW 值保存在其写缓冲区中。
- 接下来,两个核心都执行各自的加载操作 L1 和 L2,并获得旧值 0。
- 最后,两个核心的写缓冲区都用新存储的值 NEW 更新内存。
最终结果是 (r1, r2) = (0, 0)。正如我们在上一章中看到的,这是顺序一致性(SC)所禁止的执行结果。多核情形下,没有写缓冲区时,硬件是顺序一致的,但有了写缓冲区,就不再是顺序一致的,这使得写缓冲区在多核处理器中在架构上可见。【注,缓冲的延时导致乱序】
对于写缓冲区可见性的一种反应是关闭它们,但由于潜在的性能影响,厂商一直不愿意这样做。另一种选择是使用激进的、推测性的 SC 实现,使写缓冲区再次不可见,但这样做会增加复杂性,并且可能会浪费电力来检测违规和处理错误推测。
SPARC 和后来的 x86 选择的方案是放弃 SC,转而采用一种允许在每个核心直接使用先进先出(FIFO)写缓冲区的内存一致性模型。这种新模型 TSO 允许出现 "(r1, r2) = (0, 0)" 的结果。这个模型让一些人感到惊讶,但事实证明,对于大多数编程习惯用法,它的行为与 SC 类似,并且在所有情况下都有明确的定义。【注,稍加同步即可达到程序的正确性】
4.2 TSO/x86 的基本思想
随着执行的进行,SC 要求每个核心为所有四种连续操作组合保留其加载和存储的程序顺序:
(SC 的四大约束)
- 加载→加载
- 加载→存储
- 存储→存储
- 存储→加载 /* SC 包含但 TSO 省略 这个约束*/
TSO 包含前三个约束,但不包含第四个【注,这就允许写缓冲这种优化的存在?由于3,此写缓冲仅限于FIFO的】。对于大多数程序来说,这个省略并不重要。表 4.2 重复了上一章表 3.1 中的示例程序。在这种情况下,TSO 允许与 SC 相同的执行,因为 TSO 保留了核心 C1 的两个存储操作和核心 C2 的两个(或更多)加载操作的顺序。图 4.1(与上一章的图 3.2 相同)说明了这个程序的执行情况。

更一般地说,对于遵循以下模式的常见编程习惯用法,TSO 的行为与 SC 相同:
- C1 加载并存储到内存位置 D1,...,Dn(通常是数据),
- C1 存储到 F(通常是同步标志)以指示上述工作已完成,
- C2 从 F 加载以观察上述工作已完成(有时先自旋,通常使用 RMW 指令),以及
- C2 加载并存储到部分或全部内存位置 D1,...,Dn。
然而,TSO 允许一些非 SC 的执行。在 TSO 下,表 4.1 中的程序(重复上一章的表 3.3)允许图 4.2 中所示的所有四种结果。在 SC 下,只有前三种是合法结果(如前一章的图 3.3 所示)。图 4.2d 中的执行说明了一种符合 TSO 但违反 SC 的执行,因为它不遵守第四个(即存储→加载)约束。省略第四个约束允许每个核心使用写缓冲区。请注意,第三个约束意味着写缓冲区必须是 FIFO(而不是合并的,例如)以保留存储 - 存储顺序。

程序员(或编译器)可以通过在核心 C1 的 S1 和 L1 之间以及核心 C2 的 S2 和 L2 之间插入 FENCE 指令来防止图 4.2d 中的执行。在核心 Ci 上执行 FENCE 可确保 FENCE 之前(按程序顺序)的 Ci 的内存操作在内存顺序中先于 FENCE 之后的 Ci 的内存操作。使用 TSO 的程序员很少使用 FENCE(又称内存屏障),因为 TSO"为大多数程序做了正确的事情"【注,但这种机器存在fence 能力,以备不时之需】。尽管如此,FENCE 在下一章讨论的宽松模型中起着重要作用【注,FENCE 在 TSO 中也起着重要作用吧】。
TSO 确实允许一些非直观的执行结果。表 4.3 说明了表 4.1 中程序的修改版本,其中核心 C1 和 C2 分别制作了 x 和 y 的本地副本。许多程序员可能会假设,如果 r2 和 r4 都等于 0,那么 r1 和 r3 也应该为 0,因为存储操作 S1 和 S2 必须在加载操作 L2 和 L4 之后按内存顺序插入。然而,图 4.3 说明了一种执行情况,其中 r1 和 r3 绕过了每个核心写缓冲区中的 NEW 值【注,不会发生】。事实上,为了保留单线程顺序语义,每个核心必须按程序顺序看到自己存储操作的效果,即使其他核心尚未观察到该存储。因此,在所有 TSO 执行下,本地副本 r1 和 r3 将始终设置为 NEW 值【注,即前边常提到的转储】。


4.3 TSO/X86 的形式化定义
本节将对 TSO 进行更精确的定义,相比 3.5 节中的 SC 定义,仅需做出三处修改:
TSO 执行需满足以下条件:
程序顺序约束 (r1, r2) = (0, 0)
所有核心必须按程序顺序 <p 将其加载和存储操作插入全局内存顺序 <m 中,无论操作是否针对同一地址(即 a==b 或 a≠b)。具体分为四种情况:
1. 若 L(a) <p L(b),则 L(a) <m L(b) /* 加载→加载 */
2. 若 L(a) <p S(b),则 L(a) <m S(b) /* 加载→存储 */
3. 若 S(a) <p S(b),则 S(a) <m S(b) /* 存储→存储 */
4. 若 S(a) <p L(b),则 S(a) <m L(b) /* 存储→加载 */ /* 修改 1:允许 FIFO 写缓冲区 */
加载值的来源 旁路转储
每个加载操作必须从其之前对同一地址的最后一次存储中获取值:
1. L(a) 的值 = MAX<m {S (a) | S (a) <m L (a)} 的值 /* 修改 2:需要旁路机制 */
2. L(a) 的值 = MAX<m {S (a) | S(a) <m L(a) 或 S(a) <p L(a)} 的值
最后这个略显复杂的公式表明,加载操作的值来自以下两种情况的最后一次存储:(a) 在内存顺序中先于该加载操作,或(b) 在程序顺序中先于该加载操作(但可能在内存顺序中后于它),
且情况 (b) 优先(即写缓冲区的旁路机制优先于内存系统的其他部分)【注,程序顺序在内存顺序中被FIFO写缓冲延时,导致程序顺序不能体现在内存顺序中,但最终不影响 Load的到的值】。
FENCE 指令的定义
必须增强第 1 点以定义 FENCE 指令的行为: /* 修改 3:FENCE 指令对所有操作有排序效果 */
由于 TSO 已要求除 “存储→加载” 外的所有顺序【注,存储被FIFO buffer 住了】,因此也可将 TSO 的 FENCE 指令简化定义为仅对以下操作排序:
我们选择让 TSO 的 FENCE 指令冗余排序所有操作,因为这样做并无坏处,且使它们与下一章为更宽松模型定义的 FENCE 指令保持一致。
. 若 L (a) <p FENCE,则 L (a) <m FENCE /* 加载→FENCE */
.若 S (a) <p FENCE,则 S (a) <m FENCE /* 存储→FENCE */
. 若 FENCE <p FENCE,则 FENCE <m FENCE /* FENCE→FENCE */
. 若 FENCE <p L (a),则 FENCE <m L (a) /* FENCE→加载 */
. 若 FENCE <p S (a),则 FENCE <m S (a) /* FENCE→存储 */
由于 TSO 已经要求除 "存储→加载" 之外的所有顺序约束,因此也可以将 TSO 的 FENCE 指令仅定义为对以下操作排序:
. 若 S (a) <p FENCE,则 S (a) <m FENCE /* 存储→FENCE */
. 若 FENCE <p L (a),则 FENCE <m L (a) /* FENCE→加载 */
【注,当程序中出现FENCE时,编译器会在其他情况下忽略该 FENCE 的存在。】
我们选择让 TSO 的 FENCE 指令冗余地对所有操作进行排序,因为这样做不会造成损害,同时能使它们与下一章中为更宽松内存模型定义的 FENCE 指令保持一致性。
表4.4总结了 TSO 的排序规则,该表与 SC 模型的对应表格(表3.4)存在两个重要差异:其一,当操作#1是存储而操作#2是加载时,交叉项标记为"B"而非"X"——若这两个操作针对相同地址,即使它们以非程序顺序进入内存顺序,加载操作也必须获取刚存储的值【注,旁路转储】;其二,该表包含了 SC 模型中不必要的 FENCE 指令,因为 SC 系统本身的表现就像每个操作前后都已隐含了 FENCE 指令。

学界普遍认为x86内存模型等同于 TSO 模型(针对常规可缓存内存和普通指令),但据我们所知,AMD和英特尔都未对此作出正式保证,也未发布过形式化的x86内存模型规范。AMD和英特尔通过示例和描述性文字来公开定义x86内存模型,Sewell等人[15]的第2节对此进行了精要总结。所有示例都符合TSO模型,所有文字描述也与TSO保持一致。唯有当x86内存模型的形式化描述公开可用时,这种等价性才能被严格证明。反之,若能找到反例——即x86允许而TSO禁止的执行情况,或TSO允许而x86禁止的执行情况——便可推翻这种等价性。
Sewell等人[15]的研究支持"x86等同TSO"的观点(该研究在 CACM 发表概要,其他文献[9,14]有更详细论述)。作者特别提出了 x86-TSO 模型,并通过两种等价形式予以证明:第一种形式采用抽象机器,类似于下节图4.4a的结构,但增加了用于模拟x86锁定指令的全局锁;第二种形式采用标记转移系统。前者便于从业者理解,后者则利于形式化验证。一方面,x86-TSO 模型与 x86 规范中的非正式规则及测试案例相符;另一方面,在多个AMD和英特尔平台上的实证测试未发现违反该模型的情况(但这不能证明绝对不存在)。总之,我们与Sewell团队一致,敦促 x86 软硬件开发者采用这种明确且易理解的 x86-TSO 模型。

4.4 TSO/X86 的实现
TSO/x86 的实现与 SC 类似,但每个核心增加了 FIFO 写缓冲区。图 4.4a 对图 3.4 的切换机制进行了更新,以支持 TSO,其工作流程如下:
- 加载和存储按程序顺序 < p 离开核心。
- 加载操作可从写缓冲区旁路值,或像 SC 一样等待切换。
- 存储操作进入 FIFO 写缓冲区的尾部;若缓冲区已满,则暂停核心。
- 当切换器选择核心 Ci 时,执行下一个加载操作或写缓冲区头部的存储操作。
在 3.7 节中,我们表明对于 SC,切换器可被缓存一致性内存系统替代,并论证了核心可以是推测性的和 / 或多线程的,且非绑定预取可由核心、缓存或软件发起。如图 4.4b 所示,同样的论证适用于 TSO,只需在每个核心与缓存一致性内存系统之间插入 FIFO 写缓冲区。因此,除写缓冲外,之前关于 SC 实现的讨论均适用于 TSO,并为构建 TSO 实现提供了途径。此外,当前大多数 TSO 实现似乎仅采用上述方法:在 SC 实现的基础上插入写缓冲区。
关于写缓冲区,推测性核心具体如何实现它们的文献和产品细节超出了本章范围。例如,微架构可在物理上合并存储队列(未提交的存储)和写缓冲区(已提交的存储),和 / 或物理上分离加载和存储队列。
最后,多线程为 TSO 引入了一个微妙的写缓冲区问题。TSO 写缓冲区在逻辑上对每个线程上下文(虚拟核心)是私有的。因此,在多线程核心上,一个线程上下文绝不能从另一个线程上下文的写缓冲区旁路值。这种逻辑分离可通过为每个线程上下文设置独立的写缓冲区实现,或更常见地,通过使用带线程上下文标识符标记的共享写缓冲区实现,仅当标记匹配时才允许旁路。
回顾测验问题 4:在具有多线程核心的 TSO 系统中,线程可以从写缓冲区旁路任何值,无论该值由哪个线程写入。对还是错?
答案:错!线程只能旁路自己写入的值,其他线程必须等到该存储操作被插入内存顺序后才能看到该值。
4.4.1 原子指令的实现
TSO 中原子 RMW(读 - 修改 - 写)指令的实现问题与 SC 类似,关键区别在于 TSO 允许加载操作越过已写入写缓冲区的早期存储操作。这对 RMW 的影响是,RMW 中的 “写” 操作(即存储)可能被写入写缓冲区。
为理解 TSO 中原子 RMW 的实现,我们将 RMW 视为一个加载操作紧跟一个存储操作:
- RMW 的加载部分不能越过早期加载操作(根据 TSO 的排序规则)。乍一看,RMW 的加载部分似乎可以越过写缓冲区中的早期存储,但这是不允许的。因为若加载部分越过早期存储,则 RMW 的存储部分也必须越过该早期存储(因为 RMW 是原子对),但 TSO 中存储操作不能相互越过,因此 RMW 的加载部分也不能越过早期存储。
这些排序约束影响 RMW 的实现:
- 写缓冲区清空:RMW 的加载部分必须等待早期存储操作排序完成(即退出写缓冲区)后才能执行,因此原子 RMW 在执行加载前实际上会清空写缓冲区。
- 读写一致性权限:为确保存储部分能紧跟加载部分排序,加载部分需要获取读写一致性权限,而非普通加载所需的只读权限。
- 原子性保证:为保证 RMW 的原子性,缓存控制器在加载和存储之间不得释放对块的一致性权限。
RMW 的更优化实现是可能的。例如,只要满足以下条件,写缓冲区无需完全清空:
(a) 写缓冲区中的每个条目已在缓存中获得读写权限,并保持该权限直到 RMW 提交;
(b) 核心执行类似 MIPS R10000 的加载推测检查(3.8 节)。
从逻辑上讲,所有早期存储和加载操作将作为一个单元(有时称为 “块”)在 RMW 之前立即提交。
4.4.2 FENCE 指令的实现
支持 TSO 的系统并不强制要求存储操作与其后续(按程序顺序)的加载操作之间的顺序,尽管它们确实要求加载操作获取早期存储的值。在程序员希望这些指令有序执行的情况下,必须通过在存储和后续加载之间插入 FENCE 指令来明确指定这种顺序。FENCE 指令的语义规定,程序顺序中 FENCE 之前的所有指令必须在 FENCE 之后的任何指令之前被排序到内存中。

对于支持 TSO 的系统,FENCE 因此禁止加载操作绕过早期的存储操作。在表 4.5 中,我们重新审视表 4.1 中的示例,但添加了两条之前没有的 FENCE 指令。如果没有这些 FENCE 指令,两个加载操作(L1 和 L2)可以绕过两个存储操作(S1 和 S2),导致 r1 和 r2 都被设置为零的执行结果。添加的 FENCE 指令禁止了这种重排序,从而排除了这种执行情况。
由于 TSO 只允许一种类型的重排序,FENCE 指令的使用相对较少,因此 FENCE 指令的实现并非至关重要。一个简单的实现方式 —— 例如在执行 FENCE 时清空写缓冲区,并在早期的 FENCE 提交之前不允许后续加载执行 —— 可能提供可接受的性能。
然而,对于允许更多重排序的一致性模型(将在下一章讨论),FENCE 指令的使用更为频繁,其实现对性能可能产生重大影响。
边栏:非推测性 TSO 优化
本边栏描述了一些先进的非推测性 TSO 优化方法。
-
非推测性 TSO 重排序
已有论文表明,通过使用一致性延迟技术,加载操作 [11, 12] 和存储操作 [13] 都可以在不进行推测的情况下进行重排序,同时仍然保证 TSO。如前所述,关键挑战是确保这些延迟不会导致循环依赖从而引发死锁,上述所有论文都解决了这个问题。 -
无需清空写缓冲区的 RMW
在 4.4.1 节中,我们展示了如何通过推测将写缓冲区清空操作移出关键路径。Rajaram 等人 [10] 表明,如果重新定义 RMW 的原子性语义,同样的效果可以非推测性地实现。回顾一下,为了使 RMW 具有原子性,我们之前要求 RMW 的读和写操作必须在 TSO 全局内存顺序中连续出现。考虑以下放松条件:只要在全局内存顺序中,与 RMW 操作同一地址的写操作不出现在 RMW 的读和写操作之间,就认为 RMW 是原子的。请注意,这个放宽的定义符合 RMW 的直观定义,并且足以在同步场景中使用。同时,它允许 RMW 的实现中,RMW 的加载部分可以绕过写缓冲区中的早期存储,而无需进行 MIPS R10000 风格的加载推测检查。然而,确保 RMW 原子性需要延迟一致性操作直到写缓冲区清空,这引入了死锁风险。Rajaram 等人 [10] 展示了如何避免死锁。 -
越过 FENCE 的重排序
有几篇论文提出了优化的 FENCE 实现 [3, 4, 6, 8],这些实现允许 FENCE 之后的内存操作在 FENCE 之前的操作完成之前非推测性地提交。这些技术要么使用一致性延迟,要么使用前驱序列化,或者两者结合。
4.5 关于 TSO 的进一步阅读材料
Collier [2] 通过一种模型来描述替代的内存一致性模型(包括 IBM System/370 的模型),在该模型中,每个核心都有一个完整的内存副本,其加载操作从本地副本读取,而其存储操作根据定义模型的某些限制更新所有副本。如果用这个模型来定义 TSO,每个存储操作会立即写入自己核心的内存副本,然后可能在稍后一起更新所有其他内存。
Goodman [7] 公开讨论了处理器一致性(PC)的概念,即一个核心的存储操作按顺序到达其他核心,但不一定在同一 "时间" 到达其他核心。Gharachorloo 等人 [5] 更精确地定义了 PC。TSO 和 x86-TSO 是 PC 的特殊情况,其中每个核心立即看到自己的存储,而当任何其他核心看到一个存储时,所有其他核心也都会看到它。这个属性在下一章(5.5 节)中称为写原子性。
据我们所知,TSO 最早由 Sindhu 等人 [16] 正式定义。如 4.3 节所述,Sewell 等人 [9, 14, 15] 提出并形式化了 x86-TSO 模型,该模型似乎与 AMD 和 Intel 的 x86 文档及当前实现一致。
4.6 SC 与 TSO 的比较
现在我们已经了解了两种内存一致性模型,我们可以对它们进行比较。SC、TSO 等之间有什么关系?
- 执行结果:SC 执行是 TSO 执行的真子集;所有 SC 执行都是 TSO 执行,而有些 TSO 执行是 SC 执行,有些则不是。见图 4.5a 中的维恩图。
- 实现方式:实现遵循相同的规则:SC 实现是 TSO 实现的真子集。见图 4.5b,它与图 4.5a 相同。
更一般地说,如果所有 X 执行也是 Y 执行,但反之不成立,那么内存一致性模型 Y 比内存一致性模型 X 更宽松(更弱)。如果 Y 比 X 更宽松,那么所有 X 的实现也都是 Y 的实现。两个内存一致性模型也可能是不可比较的,因为它们都允许对方禁止的执行。
如图 4.5 所示,TSO 比 SC 更宽松,但比不可比较的模型 MC1 和 MC2 更严格。在下一章中,我们将看到 MC1 和 MC2 的候选模型,包括 IBM Power 内存一致性模型的案例研究。

什么是好的内存一致性模型?
一个好的内存一致性模型应该具备 Sarita Adve 的 3P 原则 [1] 加上我们的第四个 P:
- 可编程性(Programmability):一个好的模型应该(相对)容易编写多线程程序。该模型应该对大多数用户来说是直观的,即使是那些没有阅读过详细规范的用户。它应该是精确的,以便专家能够探索其允许的极限。
- 性能(Performance):一个好的模型应该能够在合理的功耗、成本等条件下实现高性能。它应该给实现者提供广泛的选择空间。
- 可移植性(Portability):一个好的模型应该被广泛采用,或者至少提供向后兼容性,或者能够在不同模型之间进行转换。
- 精确性(Precision):一个好的模型应该用数学方法精确地定义。自然语言过于模糊,无法让专家探索其允许的极限。
SC 和 TSO 的表现如何?
根据这 4P 原则:
- 可编程性:SC 是最直观的。TSO 接近 SC,因为它对常见的编程习惯用法表现得像 SC。尽管如此,微妙的非 SC 执行可能会让程序员和工具作者陷入困境。
- 性能:对于简单核心,TSO 可以提供比 SC 更好的性能,但通过推测技术可以减小这种差异。
- 可移植性:SC 被广泛理解,而 TSO 被广泛采用。
- 精确性:SC 和 TSO 都有正式的定义。
底线是 SC 和 TSO 非常接近,特别是与下一章讨论的更复杂、更宽松的内存一致性模型相比。

2700

被折叠的 条评论
为什么被折叠?



