Download as pdf or txt
Download as pdf or txt
You are on page 1of 296

Machine Translated by Google

综合讲座
计算机架构

Natalie  Enright  Jerger  和  Margaret  Martonosi,
系列编辑
Machine Translated by Google
Machine Translated by Google

A  首先在哪里
内存一致性和缓存一致

第二版
Machine Translated by Google
Machine Translated by Google

综合讲座
计算机架构
编辑
Natalie  Enright  Jerger,
多伦多大学Margaret  
Martonosi, 普林斯顿大学名誉创始编辑Mark  D.  

Hill,
威斯康星大学麦迪逊分校

Synthesis  Lectures  on  Computer  Architecture出版  50  到  100  页的出版物,主题涉及设计、
分析、选
择和互连硬件组件以创建满足功能、 性能和成本目标的计算机的科学和艺术。 范围将主要遵循顶级计算机体系结
构会议的范围, 例如  ISCA、 HPCA、MICRO  和  ASPLOS。

内存一致性和缓存一致性入门, 第二版Vijay  Nagarajan、
Daniel  J.  Sorin、
Mark  
D.  Hill  和  David  A.  Wood  2020

记忆系统的创新
拉杰夫  Balasubramonian  
2019

缓存替换策略
Akanksha  Jain  和  Calvin  Lin  
2019

数据中心作为计算机:
设计仓库规模的机器,
第三版
Luiz  André  Barroso、
Urs  Hölzle  和  Parthasarathy  Ranganathan  2018

安全处理器架构设计原则
雅库布谢弗  
2018

通用图形处理器架构Tor  M.  Aamodt、 Wilson  Wai  
Lun  Fung  和  Timothy  G.  Rogers
2018
Machine Translated by Google

iv

为异构系统编译算法
Steven  Bell、
Jing  Pu、
James  Hegarty  和  Mark  Horowitz  
2018

虚拟内存的架构和操作系统支持
Abhishek  Bhattacharjee  和  Daniel  Lustig  
2017

面向计算机架构师的深度学习
Brandon  Reagen、
Robert  Adolf、
Paul  Whatmough、
Gu‑Yeon  Wei  和  David  Brooks  2017

片上网络,
第二版
Natalie  Enright  Jerger、
Tushar  Krishna  和  Li‑Shiuan  Peh  
2017

时空神经网络的时空计算James  E.  Smith  2017

虚拟化的硬件和软件支持
Edouard  Bugnion、
Jason  Nieh  和  Dan  Tsafrir  
2017

数据中心设计与管理:
计算机架构师的视角Benjamin  C.  Lee  2016

内存层次结构中的压缩入门Somayeh  Sardashti、
Angelos  Arelakis、
Per  Stenström  和  David  A.  Wood  2015

硬件加速器的研究基础设施
Yakun  Sophia  Shao  和  David  Brooks  
2015

分析分析
Rajesh  Bordawekar、
Bob  Blainey  和  Ruchir  Puri  
2015

可定制的计算
Yu‑Ting  Chen,  Jason  Cong,  Michael  Gill,  Glenn  Reinman,  and  Bingjun  Xiao  
2015  
Machine Translated by Google

芯片堆叠架构
Yuan  Xie  and  Jishen  Zhao  
2015  

单指令多数据执行Christopher  J.  Hughes  
2015

节能计算机架构:
最新进展
Magnus  Själander、
Margaret  Martonosi  和  Stefanos  Kaxiras  2014

计算机系统的  FPGA  加速仿真Hari  Angepat、 Derek  
Chiou、
Eric  S.  Chung  和  James  C.  Hoe
2014

A  Primer  on  Hardware  Prefetching  
Chapter  of  Philosophy  and  Thomas  F.  
Wenisch  2014

片上光子互连: 计算机架构师的观点Christopher  J.  Nitta、
Matthew  K.  
Farrens  和  Venkatesh  Akella  2013

计算机体系结构中的优化和数学建模
Tony  Nowatzki、
Michael  Ferris、
Karthikeyan  Sankaralingam、
Cristian  Estan、
Nilay  Vaish  和
大卫·伍德  2013

计算机架构师的安全基础Ruby  B.  Lee  
2013

数据中心作为计算机:
仓库规模设计简介
机器,
第二版
Luiz  André  Barroso、
Jimmy  Clidaras  和  Urs  Hölzle  2013

共享内存同步Michael  L.  Scott  
2013

针对电压变化的弹性架构设计
Vijay  Janapa  Reddy  和  Meeta  Sharma  Gupta  2013
Machine Translated by Google

我们

多线程架构Mario  
Nemirovsky  和  Dean  M.  Tullsen
2013

通用图形处理单元的性能分析和调优
(通用图形处理器)

Hyesoon  Kim、
Richard  Vuduc、
Sara  Baghsorkhi、
Jee  Choi  和  Wen‑mei  Hwu  2012

自动并行化:
基本编译器技术概述Samuel  P.  Midkiff  2012

相变存储器: 从设备到系统Moinuddin  K.  Qureshi、
Sudhanva  Gurumurthi  和  Bipin  Rajendran  2011

多核缓存层次结构Rajeev  
Balasubramonian、
Norman  P.  Jouppi  和  Naveen  Muralimanohar  2011

内存一致性和缓存一致性入门Daniel  J.  Sorin、
Mark  D.  Hill  
和  David  A.  Wood  2011

动态二进制修改:
工具、
技术和应用
金黑泽尔伍德  2011

计算机架构师的量子计算, 第二版Tzvetan  S.  Metodi、
Arvin  I.  
Faruque  和  Frederic  T.  Chong  2011

高性能数据中心网络:
架构、
算法和机会
丹尼斯·阿布茨和约翰·金  2011

处理器微架构:
实现视角
安东尼奥·冈萨雷斯、
费尔南多·拉托雷和格雷戈里·马格利斯
2010

事务内存,
第二版
蒂姆·哈里斯、
詹姆斯·拉鲁斯和拉维·拉杰瓦尔  2010
Machine Translated by Google

计算机体系结构性能评估方法
利文埃克豪特  2010

可重构超级计算简介
Marco  Lanzagorta、
Stephen  Bique  和  Robert  Rosenberg  2009

片上网络
Natalie  Enright  Jerger  和  Li‑Shiuan  Peh  2009

记忆系统:
你无法避免它,
你无法忽视它,
你无法假装它
布鲁斯·雅各布  
2009

容错计算机体系结构Daniel  J.  
Sorin  2009

数据中心作为计算机:
仓库规模设计简介
机器
Luiz  André  Barroso  和  Urs  Hölzle  2009

提高能效的计算机体系结构技术
Stefanos  Kaxiras  和  Margaret  Martonosi  2008

芯片多处理器架构:
提高吞吐量和延迟的技术
Kunle  Olukotun、
Lance  Hammond  和  James  Laudon  2007

交易记忆James  R.  
Larus  和  Ravi  Rajwar  2006

计算机架构师Tzvetan  S.  Metodi  和  Frederic  T.  
Chong的量子计算
2006年
Machine Translated by Google

版权所有  ©  2020  Morgan  &  Claypool

版权所有。
未经本出版物的事先许可,
不得以任何形式或任何方式(电子、
机械、
影印、
录音或任何其他方式)
复制、
存储在检索系统中或传输本
出版物的任何部分,
印刷评论中的简短引述除外出版商。

内存一致性和高速缓存一致性入门,
第二版  Vijay  Nagarajan、
Daniel  J.  Sorin、
Mark  D.  Hill  

和  David  A.  Wood

www.morganclaypool.com

书号:
9781681737096 平装电子书
书号:
9781681737102
书号:
9781681737119 精装

DOI  10.2200/S00962ED2V01Y201910CAC049

Morgan  &  Claypool  Publishers  系列中的出版物
计算机体系结构综合讲座

第  49  讲

系列编辑:
Natalie  Enright  Jerger,
多伦多大学Margaret  Martonosi,
普林
斯顿大学名誉创始编辑:
Mark  D.  Hill,
威斯康星大学麦
迪逊系列  ISSN

打印  1935‑3235  电子  1935‑3243
Machine Translated by Google

A  首先在哪里
内存一致性和缓存一致

第二版

维杰·纳加拉詹
爱丁堡大学

Daniel  J.  Sorin杜
克大学

Mark  D.  Hill威
斯康星大学麦迪逊分校

David  A.  Wood威
斯康星大学麦迪逊分校

计算机体系结构综合讲座  #49

&MC摩根 &克莱普尔 出版商


Machine Translated by Google

摘要许多现代计
算机系统,
包括同构和异构架构,都支持硬件共享内存。在共享内存系统中,每个处理器内
核都可以读取和写入单个共享地址空间。对于共享内存机器,内存一致性模型定义了其内
存系统在架构上可见的行为。一致性定义提供有关加载和存储(或内存读取和写入)以及
它们如何作用于内存的规则。作为支持内存一致性模型的一部分,许多机器还提供缓存一
致性协议,以确保多个缓存数据副本保持最新。

这本入门书的目的是让读者对一致性和连贯性有一个基本的了解。
这种理解既包括必须
解决的问题,
也包括各种解决方案。我们既提供了高级概念,
也提供了来自现实世界系统
的具体示例。

第二版反映了自第一版以来十年的进步, 除了其他更适度的变化外, 还包括两个新


章节:
一个关于非  CPU  加速器的一致性和连贯性(重点是  GPU),
一个指向正式工作和
关于一致性和连贯性的工具。

关键词计算机体
系结构、 内存一致性、缓存一致性、
共享内存、
内存系统、
多核处理器、
异构架构、
GPU、
加速器、 语义、
验证
Machine Translated by Google

内容
第二版序言。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .十七

第一版序言。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 十九

1一致性和连贯性简介。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1个

1.1  一致性(又名内存一致性、
内存一致性模型或内存模型)。 . . . . . . . . . . . . . . . . . .  2  1.2  一致性(又
名高速缓存一致性)。 . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . 4个

1.3  异构系统的一致性和连贯性。 . . . . . . . . . . . . . . . . .  6个

1.4  指定和验证内存一致性模型和缓存
连贯性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  6个

1.5  一致性和连贯性测验。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  6个

1.6  本入门书不做什么。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  7


1.7  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  8个

2相干基础知识。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  9

2.1  基线系统模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  9

2.2  问题:
不连贯如何可能发生。 . . . . . . . . . . . . . . . . .  10
2.3  缓存一致性接口。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.4(一致性不可知)
相干不变量。 . . . . . . . . . . . . . . . . . . . . . . . .  12

2.4.1  保持相干不变量。 . . . . . . . . . . . . . . . . . . . . . . . . 14

2.4.2  一致性的粒度。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14


2.4.3  什么时候一致性相关? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  15
2.5  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  16

3内存一致性动机和顺序一致性。 . . . . . . . . . . . .  17

3.1  共享内存行为的问题。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  17

3.2  什么是内存一致性模型? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  20

3.3  一致性与连贯性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  21

3.4  顺序一致性(SC)
的基本思想。 . . . . . . . . . . . . . . . . . . . . . . . . . . .  22
3.5  一点  SC  形式主义。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  23
Machine Translated by Google

十二

3.6  朴素的  SC  实现。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  26

3.7  具有高速缓存一致性的基本  SC  实现。 . . . . . . . . . . . . . . . . . . .  27

3.8  具有高速缓存一致性的优化  SC  实现。 . . . . . . . . . . . . . . . .  29

3.9  SC  的原子操作. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  33

3.10  综合起来:
MIPS  R10000。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  34

3.11  关于  SC  的进一步阅读。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  35


3.12  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  36

4  Total  Store  Order  和  x86  内存模型。 . . . . . . . . . . . . . . . . . . . . . . . . .  39


4.1  TSO/x86  的动机。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  39
4.2  TSO/x86  的基本思想。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  40
4.3  一点  TSO/x86  形式主义。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  42

4.4  实施  TSO/x86。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  

4.4.1  执行原子指令。 . . . . . . . . . . . . . 47. . . . . . . . . . . . .  47

4.4.2  实施围栏。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  48岁

4.5  关于  TSO  的进一步阅读。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  50

4.6  比较  SC  和  TSO。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  50


4.7  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  52

5宽松的内存一致性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  55
5.1  动机。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  55

5.1.1  重新排序内存操作的机会。 . . . . . . . . . . . . . . . . .  56  5.1.2  利用重新排序的机会。 . . . .
. . . . . . . . . . . . . . . . . . . . .  57

5.2  松弛一致性模型  (XC)  示例。 . . . . . . . . . . . . . . . . . . . . . . .  58


5.2.1  XC模型的基本思想。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  59

5.2.2  在  XC  下使用栅栏的例子。 . . . . . . . . . . . . . . . . . . . . . . . . . .  60

5.2.3  形式化  XC。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  60

5.2.4  显示  XC  正确运行的示例。 . . . . . . . . . . . . . . . . . . .  62  5.3  实施  XC。 . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  65
5.3.1  XC  的原子指令。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  66
5.3.2  带  XC  的栅栏。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  67
5.3.3  警告。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  68

5.4  无数据竞争程序的顺序一致性。 . . . . . . . . . . . . . . . . . .  68

5.5  一些宽松的模型概念。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  72

5.5.1  发布一致性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  72

5.5.2  因果关系和写原子性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  73


Machine Translated by Google

十三

5.6  松弛记忆模型案例研究。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  75


5.6.1  RISC‑V  弱内存顺序  (RVWMO)。 . . . . . . . . . . . . . . . . . . .  75  5.6.2  IBM  Power。 . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  78

5.7  进一步阅读和商业松弛记忆模型。 . . . . . . . . . . . . .  81。 . . . . . . .  81
5.7.1  学术文献。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.7.2  商业模式。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  81

5.8  比较内存模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  82


5.8.1  松弛记忆模型如何相互关联以及
TSO  和  SC? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  82
5.8.2  松弛模型有多好? . . . . . . . . . . . . . . . . . . . . . . . . . . . .  82

5.9  高级语言模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  83


5.10  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  86

6个 一致性协议。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  91

6.1  大局。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  91

6.2  指定一致性协议。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  93

6.3  简单一致性协议示例。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  94

6.4  一致性协议设计空间概述。 . . . . . . . . . . . . . . . . . . . . . . .  


6.4.1  国家。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96。 . . . . . . .  97
6.4.2  交易。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  101
6.4.3  主要协议设计选项。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  103
6.5  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  105

7窥探一致性协议。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  107

7.1  窥探简介。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  107

7.2  基线侦听协议。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

7.2.1  高级协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . .  112


7.2.2  简单的监听系统模型:
原子请求, Atomic
交易。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  113
7.2.3  基线侦听系统模型:
非原子请求、
原子事务。 . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  117
7.2.4  运行示例。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  121
7.2.5  协议简化。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  122

7.3  添加独占状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  123


7.3.1  动机。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  123
7.3.2  进入独占状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  123
7.3.3  协议的高级规范。 . . . . . . . . . . . . . . . . . . . . . . . .  124
Machine Translated by Google

十四

7.3.4  详细规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  126


7.3.5  运行示例。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  126

7.4  添加拥有状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  126


7.4.1  动机。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  
7.4.2  高级协议规范。 . . . . . . . . . . . . . . 128。 . . . . . . . . . . .  129
7.4.3  详细的协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  130
7.4.4  运行示例。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  132
7.5  非原子总线 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  132
7.5.1  动机。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  132
7.5.2  有序响应与无序响应。 . . . . . . . . . . . . . . . . . . . . . .  133
7.5.3  非原子系统模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  134
7.5.4  带有拆分事务总线的  MSI  协议。 . . . . . . . . . . . . . . . .  135  7.5.5  具有拆分事务总线的优化的
非停顿  MSI  协议。 .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  140

7.6  公交互联网络优化。 . . . . . . . . . . . . . . . . . . .  142


7.6.1  数据响应的独立非总线网络。 . . . . . . . . . . . . . . .  142  7.6.2  一致性请求的逻辑总线。 . . . .
. . . . . . . . . . . . . . . . . . . .  143
7.7  案例研究。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144  
7.7.1  太阳星火  E10000。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
7.7.2  IBM  Power5。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  145

7.8  窥探的讨论和未来。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  148


7.9  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  148

8目录一致性协议。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  151

8.1  目录协议简介。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  151

8.2  基线目录系统。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  153


8.2.1  目录系统模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  153
8.2.2  高级协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . .  153
8.2.3  避免死锁。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  155
8.2.4  详细的协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  158
8.2.5  协议操作。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  159
8.2.6  协议简化。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  161

8.3  添加独占状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  162


8.3.1  高级协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . .  162
8.3.2  详细的协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  164

8.4  添加拥有状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  164


8.4.1  高级协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . .  164
Machine Translated by Google

十五

8.4.2  详细的协议规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . .  168

8.5  表示目录状态。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  168


8.5.1  粗目录。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  168
8.5.2  有限指针目录。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  171

8.6  目录组织。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  


8.6.1  由  DRAM  支持的目录缓存  8.6.2  包含目录缓 . . . . . . . . . . . . . . . . . . 172。 . . . . . .  173
存。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  174
8.6.3  空目录缓存(无后备存储)。 . . . . . . . . . . . . . . .  176  8.7  性能和可扩展性优化。 . . . . .
. . . . . . . . . . . . . . . . . . . .  177
8.7.1  分布式目录。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  177
8.7.2  非停顿目录协议。 . . . . . . . . . . . . . . . . . . . . . . . . . .  178
8.7.3  没有点对点排序的互连网络。 . . . .  180  8.7.4  状态  S  中块的静默与非静默驱逐。 . . . . . . . . . . . .  
182
8.8  案例研究。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  183
8.8.1  SGI  起源  2000。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  183
8.8.2  相干超传输。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  185
8.8.3  超级运输辅助。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  187
8.8.4  英特尔  QPI。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  187

8.9  目录协议的讨论和未来。 . . . . . . . . . . . . . . . . . . . .  189


8.10  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  189

9  个连贯性高级主题。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  191

9.1  系统模型。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  191


9.1.1  指令缓存。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  191
9.1.2  翻译后备缓冲器  (TLB)。 . . . . . . . . . . . . . . . . . . . . . . .  192
9.1.3  虚拟缓存。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  192
9.1.4  直写缓存。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  193
9.1.5  相干直接内存访问  (DMA)。 . . . . . . . . . . . . . . . . . . . .  194
9.1.6  多级缓存和分层一致性协议。 . . . . . .  195

9.2  性能优化。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  198


9.2.1  迁移共享优化。 . . . . . . . . . . . . . . . . . . . . . . . . . . .  198
9.2.2  虚假共享优化。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  199

9.3  保持活力。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  200


9.3.1  死锁。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  
9.3.2  活锁。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200。 . . . . . .  203
9.3.3  饥饿。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  206
9.4  代币一致性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  207
Machine Translated by Google

十六

9.5  一致性的未来。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  207


9.6  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  207

10异构系统的一致性和连贯性。 . . . . . . . . . . . . . . . .  211

10.1  GPU  一致性和连贯性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  211  


10.1.1  早期  GPU:
架构和编程模型。 . . . . . . . . . . .  212  10.1.2  大图:
GPGPU  一致性和连贯
性。 . . . . . . . . . . . . .  216  10.1.3  时间一致性。 . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  217
10.1.4  发布一致性导向的一致性。 . . . . . . . . . . . . . . . . . . . . .  226

10.2  不仅仅是  GPU  的异质性。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  237


10.2.1  异构一致性模型。 . . . . . . . . . . . . . . . . . . . . . . . .  237
10.2.2  异构一致性协议。 . . . . . . . . . . . . . . . . . . . . . . . .  240

10.3  进一步阅读。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  247


10.4  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  247

11指定和验证内存一致性模型和缓存一致性。 . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  251

11.1  规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  251


11.1.1  操作规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  252
11.1.2  公理规范。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  256

11.2  探索内存一致性模型的行为。 . . . . . . . . . . . . . .  260


11.2.1  ⽯蕊试验。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  260
11.2.2  探索。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  261

11.3  验证实施。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  262


11.3.1  形式化方法。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  262
11.3.2  测试。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  265

11.4  历史和进一步阅读。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  267


11.5  参考资料。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  267

作者的传记。 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .  273
Machine Translated by Google

十七

第二版前言
由于增加了两章和其他适度的变化, 这本入门指南的第二版与已有近十年历史的第一版(2011  年) 不
同。 新的第10章介绍了非  CPU  加速器(主要是通用  GPU)
的新兴工作, 这些加速器通常同时实现一致
性和连贯性。 新的第11章指出了关于一致性和连贯性的正式工作和工具, 这些工作和工具自  Primer  第
一版以来有了很大的进步。 其他变化较为温和, 包括以下内容:  第2章扩展了一致性的定义, 以接纳第
10  章中类似  GPU  的解决方案;
第3章和第  4章分别讨论了  SC  和  TSO  执法的最新进展;
第5章增加了  
RISC‑V  案例研究。

这项工作的第一版得到了美国国家科学基金会(CNS‑0551401、 CNS0720565、
CCF‑0916725、
CCF‑0444516  和  CCF‑0811290)、Sandia/DOE  (#MSN123960/DOE890426)、
半导体研究的部分
支持公司(合同  2009‑HJ‑1881) 和威斯康星大学(Kellett  奖授予  Hill)。 此处表达的观点不一定代
表  NSF、
Sandia、 DOE  或  SRC  的观点。
第二版由  EPSRC  资助, 授权给美国爱丁堡大学  EP/M027317/1

国家科学基金会(CNS‑1815656、 CCF‑1617824、 CCF‑1734706) 和约翰·P·莫格里奇希尔教授职位。


此处表达的观点仅为作者的观点。
作者感谢  Vasileios  Gavrielatos、 Olivier  Giroux、Daniel  Lustig、
Kelly  Shaw、
Matthew  Sinclair  和  2019  年春季版  UW‑Madison  研究生并行计算机体系结构  (CS/ECE  757)  的
学生, 感谢他们富有洞察力和详细的评论, 改进了第二版, 即使作者对其最终版负责

内容。

作者还感谢  Margaret  Martonosi、 Natalie  Enright  Jerger、
Michael  Morgan、
以及他们的编辑团队, 他们促成了本版入门指南的制作。
Vijay  感谢  Nivi、
Sattva、
Swara、
他的父母和姻亲的爱。  Vijay  还感谢  IIT  Madras  支持他的休
假,大部分新章节都是在这段时间写成的。 特别是, 他深情地回忆起钦奈“冬天” 高峰期在  IIT  
Chemplast  场地的板球网训练。

Dan  感谢  Deborah、
Jason  和  Julie,
感谢他们的爱和容忍他抽出时间来编写这本入门读物的另
一版。
Mark  想感谢  Sue、
Nicole  和  Gregory  的爱与支持。
Machine Translated by Google

xviii  第二版前言

David  感谢他的合著者容忍他在截止日期前挑战的工作风格, 感谢他的父母  Roger  和  Ann  
Wood  激励他成为第二代计算机科学教授, 感谢  Jane、
Alex  和  Zach  帮助他记住生活的意义.

Vijay  Nagarajan、
Daniel  J.  Sorin、
Mark  D.  Hill  和  David  A.  Wood  
2020  年  1  月
Machine Translated by Google

十九

第一版序言
本入门读物面向那些非正式地遇到过内存一致性和高速缓存一致性,
但现在想更详细地了解它们所
包含的内容的读者。
这些听众包括计算机行业的专业人士以及初级研究生。

我们希望读者熟悉计算机体系结构的基础知识。不必记住  Tomasulo  算法或类似细节的细节,
但我们确实希望读者了解架构状态、动态指令调度(乱序执行)以及如何使用缓存来减少访问存储的
平均延迟等问题结构。

这本入门书的主要目标是让读者对一致性和连贯性有一个基本的了解。这种理解既包括必须
解决的问题,也包括各种解决方案。
我们既提供了高级概念,也提供了来自现实世界系统的具体示例。
本入门读物的第二个目标是让读者意识到一致性和连贯性是多么复杂。如果读者只是简单地发现他们
不知道的东西 而不是真正地学习它 这种发现仍然是一个巨大的好处。此外,由于这些主题如此广
泛和复杂,因此无法详尽地涵盖这些主题。
本入门书的目标不是深入涵盖所有主题,而是涵盖基础知识
并告知读者他们可能希望更深入地研究哪些主题。

我们非常感谢在本入门书的开发过程中得到的帮助和支持。 我们感谢  Blake  Hechtman  实施
和测试(和调试!) 本入门中的所有一致性协议。 读者很快就会发现, 一致性协议很复杂, 我们不会信
任任何未经测试的协议, 因此  Blake  的工作非常有价值。  Blake  使用  Wisconsin  GEMS  模拟基础
设施[http://www.cs.wisc.edu/gems/]  实施并测试了所有这些协议。

感谢  Trey  Cain  和  Milo  Martin  审阅本入门手册的早期草稿并就入门手册中的各种主题进行
有益的讨论。 感谢  Newsha  Ardalani、 Arkaprava  Basu、 Brad  Beckmann、
Bob  Cypher、
Singh、
Venkatanathan  Varadarajan、 Derek  Williams  和  Meng  Zhang  对入门指南提供的额外反馈。 虽
然我们的审阅者提供了很好的反馈, 但他们可能同意也可能不同意本入门读物的所有最终内容。

这项工作部分得到国家科学基金会(CNS  0551401、 CNS‑0720565、
CCF‑0916725、
CCF‑0444516  和  CCF‑0811290)、
Sandia/DOE(#MSN123960/DOE890426)、 半导体研究公司
(合同  2009‑  HJ  1881)  和威斯康星大学(Kellett  奖授予  Hill)。
此处表达的观点不一定代表  NSF、
Sandia、DOE  或  SRC  的观点。
Machine Translated by Google

xx  第一版序言

Dan  感谢  Deborah、
Jason  和  Julie,
感谢他们的爱和容忍他抽出时间来完成另一个综合讲座。
丹首先感谢他的叔叔索尔帮助激励他成为一名工程师。 最后,Dan  谨以此书纪念  Rusty  Sneiderman,
他是  30  年的挚友, 所有有幸认识他的人都会非常怀念他。

Mark  想感谢  Sue、
Nicole  和  Gregory  的爱与支持。
David  感谢他的合著者容忍他在截止日期前挑战的工作风格, 感谢他的父母  Roger  和  Ann  
Wood  激励他成为第二代计算机科学教授, 感谢  Jane、
Alex  和  Zach  帮助他记住生活的意义.

Daniel  J.  Sorin、
Mark  D.  Hill  和  David  A.  Wood  
2011  年  11  月
Machine Translated by Google

1个

第1章

一致性和连贯性简介

许多现代计算机系统和大多数多核芯片(芯片多处理器)都支持硬件共享内存。 在共享内
存系统中,每个处理器内核都可以读取和写入单个共享地址空间。这些设计寻求各种优良
特性,
例如高性能、低功耗和低成本。当然,
在不首先提供正确性的情况下提供这些良好属
性是没有价值的。正确的共享内存在手波级别上似乎很直观,但是,
正如本讲座将帮助展
示的那样,甚至在定义共享内存系统正确的含义时也存在一些微妙的问题, 以及在设计正
确的共享内存实现。此外,
这些微妙之处必须在硬件实现中得到掌握, 因为修复错误的成
本很高。即使是学者也应该掌握这些微妙之处,以使他们提出的设计更有可能奏效。

设计和评估正确的共享内存系统需要架构师了解内存一致性和缓存一致性, 这是
本入门手册的两个主题。 内存一致性(一致性、 内存一致性模型或内存模型)
是对共享内
存正确性的一种精确的、 架构上可见的定义。 一致性定义提供有关加载和存储(或内存读
取和写入) 以及它们如何作用于内存的规则。理想情况下,一致性定义应该简单易懂。 但是,
定义共享内存正确运行的含义比定义单线程处理器内核等的正确行为更微妙。 单个处理
器内核的正确性标准将行为划分为一个正确的结果和许多不正确的选择。 这是因为处理
器的体系结构要求线程的执行将给定输入状态转换为单个明确定义的输出状态, 即使在
无序内核上也是如此。 然而,共享内存一致性模型涉及多个线程的加载和存储, 并且通常
允许许多正确的执行而不允许许多(更多) 不正确的执行。多个正确执行的可能性是由于  
ISA  允许多个线程并发执行, 通常来自不同线程的指令有许多可能的合法交错。大量正确
的执行使以前简单的确定执行是否正确的挑战变得复杂。 然而,
必须掌握一致性以实现共
享内存, 并且在某些情况下,编写使用它的正确程序。

微架构 处理器内核和共享内存系统的硬件设计 必须强制执行所需的一致性模


型。
作为此一致性模型的一部分
Machine Translated by Google

2  1.  一致性和连贯性简介

支持,
硬件提供缓存一致性(或一致性)。在具有缓存的共享内存系统中, 当其中一个处理器
更新其缓存值时,
缓存值可能会过时(或不连贯)。  Coherence  试图使共享内存系统的缓
存在功能上与单核系统中的缓存一样不可见;它通过将处理器的写入传播到其他处理器的缓
存来实现。
值得强调的是,与定义共享内存正确性的体系结构规范一致性不同, 一致性是支持一
致性模型的一种手段。

尽管一致性是本入门的第一个主要主题,但我们从第2章开始简要介绍一致性,
因为一致
性协议在提供一致性方面起着重要作用。第2章的目标是充分解释一致性以了解一致性模型如
何与一致性缓存交互,而不是探索特定的一致性协议或实现,这些主题我们推迟到第  6‑9  章本
入门的第二部分。

1.1  一致性(又称内存一致性、
内存一致性模型或内存模型)

一致性模型根据加载和存储(内存读取和写入)
定义正确的共享内存行为, 而不涉及缓存或
一致性。
为了获得关于为什么我们需要一致性模型的一些真实直觉, 请考虑一所在线发布其
课程表的大学。
假设计算机体系结构课程最初安排在  152  房间。

开课前一天, 大学注册主任决定将班级搬到  252  教室。
注册员发送一封电子邮件, 要求网站管理员更新在线时间表, 几分钟后,注册员向所有注册学
生发送一条短信, 以查看最新更新的时间表。 不难想象一个场景 比如说, 如果网站管理员太
忙而无法立即发布更新 一个勤奋的学生收到短信, 立即查看在线时间表, 并仍然观察(旧)
班级cation  Room  152.  尽管在线时间表最终更新到  252  房间并且注册员以正确的顺序执
行了“写入”, 但这位勤奋的学生以不同的顺序观察了它们, 因此去了错误的房间。一致性模
型定义了这种行为是正确的(因此用户是否必须采取其他行动来达到预期的结果) 还是不正
确的(在这种情况下系统必须排除这些重新排序)。

尽管这个人为的例子使用了多种媒体,
但在具有乱序处理器内核、写缓冲区、
预取和多个
高速缓存组的共享内存硬件中也会发生类似的行为。因此,
我们需要定义共享内存的正确性
即允许哪些共享内存行为 以便程序员知道期望什么,实现者知道他们可以提供的限制。

共享内存的正确性由内存一致性模型或更简单的内存模型指定。
内存模型规定了多线程
程序允许的行为
Machine Translated by Google

1.1.一致性  3

使用共享内存执行。
对于使用特定输入数据执行的多线程程序,内存模型指定动态加载可能返回
的值,
以及可选的内存可能的最终状态。
与单线程执行不同,通常允许多个正确的行为,这使得对
内存一致性模型的理解变得微妙。

第3章介绍了内存一致性模型的概念, 并介绍了顺序一致性(SC), 这是最强和最直观的一


致性模型。本章首先激发了指定共享内存行为的需求, 并精确定义了什么是内存一致性模型。 接下
来深入研究直观的  SC  模型,
该模型指出多线程执行应该看起来像是每个构成线程的顺序执行的
交错,
就好像线程在单核处理器上被时间复用一样。 除了这种直觉之外, 本章还将  SC  形式化并探
索以简单和积极的方式实现  SC, 并以  MIPS  R10000  案例研究结束。

在第4  章中,
我们将超越  SC, 重点关注  x86  和历史  SPARC  系统实现的内存一致性模型。
这种称为总存储顺序  (TSO)  的一致性模型的动机是希望在将结果写入缓存之前使用先进先出写
入缓冲区来保存已提交存储的结果。 这种优化违反了  SC, 但承诺有足够的性能优势来激发架构
定义  TSO,
从而允许这种优化。 在本章中, 我们展示了如何从我们的  SC  形式化中将  TSO  形式化,
TSO  如何影响实现, 以及  SC  和  TSO  如何比较。

最后, 第5章介绍了“宽松” 或“弱” 内存一致性模型。 它通过表明强模型中的大多数内存排


序是不必要的来激励这些模型。
如果一个线程更新十个数据项, 然后更新一个同步标志, 程序员通常不关心数据项是否按顺序更
新, 而只关心所有数据项在标志更新之前更新。 宽松模型试图捕捉这种增加或降低的灵活性, 以获
得更高的性能或更简单的实现。 在提供这种动机之后, 本章开发了一个示例松散一致性模型, 称
为  XC,其中程序员只有在使用  FENCE  指令(例如, 在最后一次数据更新之后但在标志写入之前
的  FENCE)
请求时才能获得顺序。 然后, 本章扩展了前两章的形式来处理  XC, 并讨论了如何实现  
XC(在内核和一致性协议之间进行了大量重新排序)。 本章随后讨论了一种许多程序员可以避
免直接考虑松弛模型的方法: 如果他们添加足够多的  FENCE  以确保他们的程序无数据竞争  
(DRF),那么大多数松弛模型将出现  SC。 通过“SC  for  DRF”,程序员可以获得(相对)简单的  
SC  正确性模型和(相对) 更高性能的  XC。
对于那些想要更深入推理的人, 本章最后通过区分获取
和发布、 讨论写入原子性和因果关系、 指向商业示例(包括  IBM  Power  案例研究)以及触及高级
语言模型(Java  和  CCC)
来结束.

回到课程表的真实世界一致性示例,
我们可以观察到电子邮件系统、
人工网络管理员和文本
消息系统的组合
Machine Translated by Google

4  1.  一致性和连贯性简介

代表一个极弱的一致性模型。 为了防止一个勤奋的学生走错房间的问题,大学注册员需要在她的
电子邮件后执行一个  FENCE  操作,
以确保在发送短信之前在线时间表已更新。

1.2  一致性(又称缓存一致性)
如果不小心,
如果多个参与者(例如,
多个内核)
可以访问数据的多个副本(例如,
在多个
缓存中)
并且至少有一次访问是写入,
则可能会出现一致性问题。
考虑一个类似于内存一致性示例的示例。 一名学生查看在线课程表,
观察到计算机体系结构课程
正在  152  房间举行(读取数据),
并将此信息复制到她手机中的日历应用程序中(缓存数据)。
随后, 大学注册处决定将班级移至  252  室,更新在线课程表(写入数据)
并通过短信通知学生。

生的数据副本现在已经过时, 我们遇到了不连贯的情况。

如果她去  152  房间,
她将找不到她的班级。
来自计算世界(但不包括计算机体系结构)
的不一致示
例包括陈旧的网络缓存和使用未更新代码存储库的程序员。

使用一致性协议可以防止对陈旧数据(不一致)的访问,该协议是由系统中的分布式参与者
集实施的一组规则。一致性协议有许多变体,但遵循一些主题,如第  6‑9  章中所述。
本质上,所有变
体都通过将写入传播到所有高速缓存使一个处理器的写入对其他处理器可见, 即保持日历与在线
计划同步。但是协议在同步发生的时间和方式上有所不同。有两类主要的一致性协议。

在第一种方法中,一致性协议确保写入同步传播到缓存。 当更新在线时间表时,一致性协议确保学
生的日历也得到更新。在第二种方法中,
一致性协议将写入异步传播到缓存, 同时仍然遵循一致性
模型。一致性协议不保证在线时间表更新时, 新值也会传播到学生的日历;但是,该协议确实确保
在文本消息到达她的手机之前传播新值。 本入门重点关注第一类一致性协议(第6‑9  章) ,
而第
10章讨论新兴的第二类。

第6章介绍了高速缓存一致性协议的概况, 并为后续章节介绍特定的一致性协议奠定了基
础。
本章涵盖了大多数一致性协议共有的问题, 包括缓存控制器和内存控制器的分布式操作以及
常见的  MOESI  一致性状态: 修改  (M)、
拥有  (O)、
独占  (E)、
共享  (S)  和无效(我)。
重要的是,本章
还介绍了我们的表驱动方法, 用于呈现具有稳定(例如  MOESI) 和瞬态相干状态的协议。 实际实现
中需要瞬态状态, 因为现代系统很少允许从一种稳定状态到另一种稳定状态的原子转换(例如,
状态  Invalid  中的读取未命中将花费一些
Machine Translated by Google

1.2.一致性(又称缓存一致性)
5
在进入共享状态之前等待数据响应的时间)。
一致性协议中的许多真正复杂性隐藏在瞬态中,
类似于
处理器核心复杂性隐藏在微架构状态中的程度。

第7章介绍了最初主导商业市场的窥探缓存一致性协议。 在手波级别,侦听协议很简单。当发生
高速缓存未命中时, 内核的高速缓存控制器仲裁共享总线并广播其请求。 共享总线确保所有控制器以
相同的顺序观察所有请求, 因此所有控制器都可以协调它们各自的分布式操作, 以确保它们保持全局
一致的状态。 然而, 窥探变得复杂, 因为系统可能使用多条总线, 而现代总线不会自动处理请求。现代总
线有仲裁队列, 可以发送单播、 流水线延迟或无序的响应。 所有这些特征都会导致更多的瞬态相干状
态。
第7章以  Sun  UltraEnterprise  E10000  和  IBM  Power5  的案例研究作为结尾。

第8章深入研究目录缓存一致性协议, 这些协议提供了扩展到更多处理器内核和其他参与者的
承诺, 而不是依赖广播的窥探协议。
有一个笑话, 计算机科学中的所有问题都可以通过一定程度的间接来解决。
目录协议支持这个笑话: 缓存未命中从下一级缓存(或内存) 控制器请求内存位置, 该控制器维护一个
目录来跟踪哪些缓存保存哪些位置。 基于所请求内存位置的目录条目, 控制器向请求者发送响应消息
或将请求消息转发给当前缓存该内存位置的一个或多个参与者。 每条消息通常都有一个目的地(即,
没有广播或多播), 但是瞬态一致性状态比比皆是, 因为从一种稳定的一致性状态到另一种稳定的状
态的转换可以生成与系统中参与者数量成比例的大量消息。 本章从基本的  MSI  目录协议开始, 然后对
其进行细化以处理  MOESI  状态  E  和  O、 分布式目录、 减少请求延迟、 近似目录条目表示等。 本章还探讨
了目录本身的设计, 包括目录缓存技术。 本章以旧的  SGI  Origin  2000  和更新的  AMD  HyperTransport、
HyperTransport  Assist  和  Intel  QuickPath  Interconnect  (QPI)  的案例研究作为结尾。

第9章涉及一些(但不是全部) 连贯性的高级主题。 为了便于解释,前面关于连贯性的章节有意将


自己限制在解释基本问题所需的最简单的系统模型上。 第9章深入研究更复杂的系统模型和优化,重点
关注侦听和目录协议的共同问题。 最初的主题包括处理指令高速缓存、 多级高速缓存、直写高速缓存、转
换后备缓冲器  (TLB)、
相干直接内存访问  (DMA)、
虚拟高速缓存和分层一致性协议。 最后,本章深入探
讨了性能优化(例如, 针对迁移共享和虚假共享) 和一个名为令牌一致性的新协议系列, 它包含目录和
窥探一致性。
Machine Translated by Google

6  1.  一致性和连贯性简介

1.3  的一致性和连贯性
异构系统

现代计算机系统主要是异构的。今天的手机处理器不仅包含多核  CPU,
还包含  GPU  和其他加
速器(例如,神经网络硬件)。
为了寻求可编程性,此类异构系统开始支持共享内存。 第10章处
理这种异质性的一致性和连贯性

新的处理器。
本章首先关注  GPU, 可以说是当今最流行的加速器。
本章观察到  GPU  最初选择不支持硬件缓存一致性,因为  GPU  是为尴尬的并行图形工作负载
而设计的,这些工作负载不会同步或共享数据。 然而,当  GPU  用于具有细粒度同步和数据共享
的通用工作负载时, 缺乏硬件缓存一致性会导致可编程性和/或性能挑战。 本章详细讨论了一些
克服这些限制的有前途的一致性替代方案 特别是解释了为什么候选协议直接强制执行一致
性模型,而不是以与一致性无关的方式实现一致性。 本章最后简要讨论了  CPU  和加速器之间的
一致性和连贯性。

1.4  指定和验证内存
一致性模型和缓存一致性

一致性模型和一致性协议是复杂而微妙的。然而,必须管理这种复杂性以确保多核是可编程的
并且它们的设计可以得到验证。
为了实现这些目标,正式指定一致性模型至关重要。正式的规范将使程序员能够清楚而详尽地
(在工具支持下)理解内存模型允许的行为以及不允许的行为。其次,精确的正式规范对于验证
实现是强制性的。

第11章首先讨论了两种指定系统的方法 公理法和操作法 重点是如何将这些方法应


用于一致性模型和一致性协议。 然后本章介绍了根据规范验证实现的技术 包括处理器流水
线和一致性协议实现。 本章讨论了正式方法和非正式测试。

1.5  一致性和连贯性测验
可以很容易地说服自己,
一个人对一致性和连贯性的了解是足够的,
没有必要阅读这本
入门读物。
为了测试是否是这种情况,
我们提供了这个突击测验。
Machine Translated by Google

1.6.本入门书不能做什么  7

问题一:
在一个保持顺序一致性的系统中,
一个核心必须按照程序顺序发出一致性请求。
对或错?  (答
案在第3.8  节)

问题2:
内存一致性模型规定了coherence  transac的合法顺序
系统。对或错?  (第3.8  节)

问题  3:
要执行原子读‑修改‑写指令(例如,
测试和设置),
一个内核必须始终与其他内核通信。
对或
错?  (第3.9  节)

问题  4:
在具有多线程内核的  TSO  系统中,
线程可能会绕过写入缓冲区中的值,
而不管哪个线程写入
了该值。 对或错?  (第4.4  节)

问题  5:
编写与高级语言的一致性模型(例如  Java) 相关的适当同步代码的程序员不需要考虑体系结
构的内存一致性模型。 对或错?  (第5.9  节)

问题  6:
在  MSI  snooping  协议中,
一个缓存块只能处于三种一致性中的一种
状态。 对或错?  (第7.2  节)

问题  7:
侦听高速缓存一致性协议要求内核在总线上进行通信。
对或错?  (第7.6  节)

问题  8:
GPU  不支持硬件缓存一致性。
因此,
他们无法强制执行内存一致性模型。
对或错?  (第10.1  
节)。

尽管本入门指南稍后会提供答案,
但我们鼓励读者在查看答案之前先尝试回答问题。

1.6  本入门书不能做什么
本讲座旨在成为连贯性和一致性的入门读物。 我们预计这些材料可以在研究生课程中用大约  10  节  
75  分钟的课讲完(例如,
第2章到第11  章各讲一节课)。

出于这个目的,
有许多内容在入门手册中没有涵盖。
其中一些包括以下内容。

‧  同步。  Coherence  使缓存不可见。一致性可以使共享内存看起来像一个单一的内存模块。

而, 程序员可能需要锁、 屏障和其他同步技术来使他们的程序有用。
读者可以参考关于共享内存同
步的综合讲座[2]。

‧  商业宽松一致性模型。
本入门书不涵盖  ARM、
PowerPC  和  RISC‑V  内存模型的微妙之处,
但确
实描述了它们提供哪些机制来执行顺序。
Machine Translated by Google

8  1.  一致性和连贯性简介

‧  并行编程。
本入门书不讨论并行编程模型,
方法或工具。
‧  分布式系统的一致性。
本入门仅限于共享内存多核内的一致性, 不涵盖一致性模型及其
对通用分布式系统的实施。 读者可以参考关于数据库复制[1]和仲裁系统[3]  的综合讲座。

1.7  参考文献
[1]  B.  Kemme、 R.  Jiménez‑Peris  和  M.  Patiño‑Martínez。
数据库复制。 数据管理综合讲座。  
Morgan  &  Claypool  出版社, 2010  年。 DOI:  10.1007/978‑1‑4614‑8265‑9_110。  8个

[2]  毫升斯科特。 共享内存同步。 计算机体系结构综合讲座。  Morgan  &  Claypool  出版社,


2013  
年。DOI:
10.2200/s00499ed1v01y201304cac023。  7

[3]  M.武科利奇。 法定人数系统: 在存储和共识中的应用。 分布式计算理论综合讲座。  Morgan  


&  Claypool  出版社,
2012  年。
DOI:  10.2200/s00402ed1v01y201202dct009。  8个
Machine Translated by Google

第2章

连贯基础
在本章中,
我们充分介绍了缓存一致性,以了解一致性模型如何与缓存交互。我们从第2.1节开始
介绍我们在本入门读物中考虑的系统模型。为了简化本章和后续章节的阐述,
我们选择了足以说
明重要问题的尽可能简单的系统模型;
我们推迟到第9章讨论与更复杂的系统模型相关的问题。

2.2节解释了必须解决的缓存一致性问题以及不一致的可能性是如何产生的。  2.3节精确定义
了缓存一致性。

2.1  基线系统模型

在本入门手册中, 我们考虑具有共享内存的多个处理器内核的系统。 也就是说,所有内核都可以


对所有(物理) 地址执行加载和存储。 基准系统模型包括单个多核处理器芯片和片外主存储器,
如图2.1  所示。多核处理器芯片由多个单线程内核组成, 每个内核都有自己的私有数据缓存, 以及
一个由所有内核共享的末级缓存(LLC)。 在本入门指南中,当我们使用术语“缓存” 时,
我们指
的是核心的私有数据缓存, 而不是  LLC。
每个内核的数据缓存都使用物理地址进行访问, 并且是
回写的。 核心和  LLC  通过互连网络相互通信。  LLC  尽管位于处理器芯片上,但在逻辑上是“内
存端缓存”, 因此不会引入另一层一致性问题。  LLC  在逻辑上就位于内存之前, 用于减少内存
访问的平均延迟并增加内存的有效带宽。

LLC  还用作片上存储控制器。
这个基线系统模型省略了许多常见的特征, 但对于本入门的大部分内容来说并不需要这
些特征。 这些功能包括指令高速缓存、 多级高速缓存、
多核共享高速缓存、虚拟寻址高速缓存、
TLB  和相干直接内存访问  (DMA)。
基准系统模型也忽略了多个多核芯片的可能性。我们稍后会
讨论所有这些特性, 但就目前而言, 它们会增加不必要的复杂性。
Machine Translated by Google

10  2.  一致性基础知识

核 核

缓存 私人的 缓存 私人的
控制器 数据缓存 控制器 数据缓存

互联网络

最后一级
有限责任公司/内存 缓存
控制器
(有限责任公司)

多核处理器芯片

主存

图  2.1:
本入门指南中使用的基线系统模型。

2.2  问题:不连贯如何
可能发生

出现不一致的可能性只是因为一个基本问题: 存在多个可以访问缓存和内存的参与者。在现代
系统中,
这些参与者是处理器内核、DMA  引擎和可以读取和/或写入缓存和内存的外部设备。

本入门的其余部分,
我们通常关注作为核心的参与者, 但值得记住的是,
可能存在其他参与者。

表2.1说明了一个不连贯的简单例子。 最初, 内存位置  A  在内存以及两个内核的本地缓存


中的值为  42。 在时间  1,
Core  1  在其缓存中将内存位置  A  的值从  42  更改为  43,
从而使  Core  
2  在其缓存中的  A  值过时。核心  2  执行  while  循环,
从其本地缓存中重复加载  A  的(陈旧) 值。

显然,这是一个不连贯的例子,
因为核心  1  的存储对核心  2  不可见, 因此  C2  卡在了  while  循环
中。
为了防止不一致,
系统必须实现缓存一致性协议, 使核心  1  的存储对核心  2  可见。 这些缓
存一致性协议的设计和实现是第  6  章到第  9  章的主要主题。
Machine Translated by Google

2.3.高速缓存一致性接口  11

表  2.1:
不连贯的例子。
假设内存位置  A  的内存值最初为  42,
并缓存在两个内核的本地缓存中。

时芯C1 核心C2
1个
S1:
A  =  43; L1:  while  (A  =  =  42);
2个 L2:  while  (A  =  =  42);
3个
L3:  while  (A  =  =  42);
4个 ⋯⋯

n
ln:  while  (A  =  =  42);

2.3  缓存一致性接口

非正式地,
一致性协议必须确保写入对所有处理器可见。
在本节中,
我们将通过它们公开的抽象
接口更正式地理解一致性协议。

处理器内核通过一致性接口(图2.2)
与一致性协议交互, 该接口提供两种方法:
(1)读取请
求方法,将内存位置作为参数并返回一个值;  (2)  一个写入请求方法,它接受一个内存位置和
一个(要写入的)值作为参数并返回一个确认。

有许多一致性协议出现在文献中并在实际处理器中使用。
我们根据它们的一致性接口的
性质将这些协议分为两类 具体来说,根据一致性模型是否与一致性模型完全分离,
或者它们
是否不可分割。

一致性不可知的一致性。
在第一类中,
写入在返回之前对所有其他内核可见。因为写入是同步传
播的,
所以第一类提供了一个与原子内存系统(没有缓存)
相同的接口。因此,
任何与一致性协
议交互的子系统 例如,
处理器核心管道 可以假设它正在与不存在高速缓存的原子内存系统
交互。
从一致性实施的角度来看,
这种一致性接口可以很好地分离关注点。

高速缓存一致性协议将高速缓存完全抽象化, 并呈现出一种原子内存的错觉 就好像高速缓存


被移除,
只有内存包含在一致性盒中(图  2.2) 而处理器核心管道强制执行由一致性模型规
范。

一致性导向的一致性。
在第二个,最近的类别中,
写入是异步传播的 因此写入可以在它对所有
处理器可见之前返回,
从而允许观察陈旧值(实时)。但是,
为了正确地执行一致性,此类中的
一致性协议必须确保写入的顺序
Machine Translated by Google

12  2.  一致性基础知识

核 核 核
⋯⋯ ⋯⋯
管道 管道 管道

读请求(⋯)
写请求 读响应(⋯)
写响应
管道 (⋯) (⋯)

一致性

私人的 私人的 私人的


缓存 缓存 缓存

有限责任公司

主存储器

图  2.2:
流水线一致性接口。

eventually  made  visible  遵守一致性模型规定的排序规则。回到图2.2,
管道和一致性协议都
强制执行一致性模型规定的顺序。 第二个类别的出现是为了支持基于吞吐量的通用图形处理单
元  (GP‑GPU), 并在本初级读物的第一版出版后获得了突出地位。  1

入门书(以及本章的其余部分)
重点介绍第一类一致性协议。
我们在异构一致性的背景下讨论第二类一致性协议(第10  章)。

2.4(一致性无关)
相干不变量

一致性协议必须满足哪些不变量才能使缓存不可见并呈现原子内存系统的抽象?

在教科书和已发表的论文中出现了几种连贯性的定义,我们不想一一列举。
相反,我们提出
了我们更喜欢的定义,因为它深入了解了一致性协议的设计。
在边栏中,我们讨论了替代定义以
及它们与我们首选定义的关系。

我们通过单写多读  (SWMR)不变量来定义一致性。
对于任何给定的内存位置,
在任何给定
的时刻,要么有一个核心可能
1对于那些关心一致性影响的人,
请注意,
使用这种方法可以强制执行各种一致性模型,
包括  SC  和  
TSO  等强模型。
Machine Translated by Google

2.4.  (一致性不可知)
相干不变量  13
写入它(并且也可以读取它) 或一些可以读取它的内核。 因此,永远不会有一个给定内存位置
可以由一个内核写入并同时由任何其他内核读取或写入的时间。 查看此定义的另一种方法是
考虑,
对于每个内存位置, 内存位置的生命周期被划分为多个时期。 在每个时期,
要么有一个核
心具有读写访问权限,
要么有一定数量的核心(可能为零) 具有只读访问权限。图2.3说明了示
例内存位置的生命周期,分为四个保持  SWMR  不变的时期。

时间
只读 读写 读写 只读
核心  2  和  5 核心3 核心一 核心  1、
2  和  3

图  2.3:
将给定内存位置的生命周期划分为多个时期。

除了  SWMR  不变性之外, 一致性还要求正确传播给定内存位置的值。 为了解释为什么价


值观很重要, 让我们重新考虑图  2.3  中的例子。 即使  SWMR  不变量成立, 如果在第一个只读时
期,
核心  2  和核心  5  可以读取不同的值, 则系统不一致。 同样,如果  Core  1  未能读取  Core  3  
在其读写期间写入的最后一个值, 或者  Core  1、
2  或  3  中的任何一个未能读取  Core  1  在其读
写期间执行的最后一次写入, 则系统是不一致的时代。

因此, 相干性的定义必须使用与值如何从一个时代传播到下一个时代有关的数据值不变
量来扩充  SWMR  不变量。
这个不变量表明一个时期开始时的内存位置的值与其最后一个读写
时期结束时的内存位置的值相同。

这些不变量还有其他等价的解释。一个著名的例子[5]根据标记来解释  SMWR  不变量。
不变量如下。
对于每个内存位置,存在至少与内核数量一样大的固定数量的令牌。 如果核心拥有所有令牌,
它可以写入内存位置。如果一个核心有一个或多个令牌,它可以读取内存位置。 因此,在任何给
定时间,不可能有一个内核正在写入内存位置,而任何其他内核都在读取或写入它。

相干不变量

1.单写多读(SWMR)
不变。
对于任何内存位置  A, 在任何给定时间,
只存在一个可以写入  
A(也可以读取它)的内核或一些只能读取  A  的内核。

2.数据值不变。一个纪元开始时的内存位置值与其最后一个读写纪元结束时的内存位置
值相同。
Machine Translated by Google

14  2.  一致性基础知识

2.4.1  保持相干不变量

上一节中介绍的一致性不变量提供了一些关于一致性协议如何工作的直觉。 绝大多数一致性协议,称
为“无效协议”,明确设计用于维护这些不变量。如果一个内核想要读取一个内存位置,它会向其他
内核发送消息以获取该内存位置的当前值,并确保没有其他内核以读写状态缓存该内存位置的副本。
这些消息结束任何活动的读写时期并开始只读时期。 如果一个核心想要写入内存位置,
它会向其他核
心发送消息以获取内存位置的当前值,如果它还没有有效的只读缓存副本,并确保没有其他核心有只
读或读写状态的内存位置的缓存副本。

这些消息结束任何活动的读写或只读时期,并开始一个新的读写时期。
本初级读物中有关缓存一致性的章节(第6  章至第  9  章)
极大地扩展了对无效协议的抽象描述,
但基
本直觉保持不变。

2.4.2  一致性的粒度

内核可以以各种粒度执行加载和存储, 通常范围为  1–64  字节。理论上,
可以以最精细的加载/存储粒
度执行一致性。然而,
在实践中,一致性通常保持在缓存块的粒度上。 也就是说,硬件在逐个缓存块的
基础上强制执行一致性。在实践中,SWMR  不变量很可能是, 对于任何内存块,
要么有一个写入器,要
么有一定数量的读取器。在典型系统中, 一个内核不可能写入块的第一个字节, 而另一个内核正在写
入该块中的另一个字节。尽管缓存块粒度很常见, 并且这是我们在本入门指南的其余部分中假设的,
但应该意识到已经有一些协议在更精细和更粗糙的粒度上保持一致性。

边栏:一致性的类一致性定义
我们对一致性的首选定义是从实现的角度来定义它 指定关于不同内核对内存位置的访
问权限以及内核之间传递的数据值的硬件强制不变性。

存在另一类定义,
从程序员的角度定义一致性,
类似于内存一致性模型如何指定体系结构
可见或加载和存储的顺序。

指定一致性的一种类似一致性的方法与顺序一致性的定义有关。 顺序一致性(Sequential  
consistency,
SC)
是我们在第  3  章中深入讨论的一种内存一致性模型, 它指定系统必须以尊重
每个线程的程序顺序的总顺序执行所有线程对所有内存位置的加载和存储。 线。
每次加载都会获
取该总订单中最近商店的价值。 与  SC  的定义类似的连贯性定义是连贯性
Machine Translated by Google

2.4.  (一致性不可知)
相干不变量  15

ent  系统必须看起来以尊重每个线程的程序顺序的总顺序执行所有线程的加载和存储到单个内
存位置。 这个定义强调了文献中一致性和一致性之间的一个重要区别:一致性是在每个内存位置
的基础上指定的, 而一致性是针对所有内存位置指定的。值得注意的是,
满足  SWMR  和数据值
不变量的任何一致性协议(与不重新排序对任何特定位置的访问的管道相结合) 也保证满足这
种类似一致性的一致性定义。

(然而, 反过来不一定成立。)
一致性的另一个定义[1,  2]  定义了具有两个不变量的一致性: (1)  每个存储最终都对所
有内核可见, 并且  (2)  对相同内存位置的写入是序列化的(即, 所有内核都以相同的顺序观察到
核心)。  IBM  在  Power  架构[4]  中采取了类似的观点,部分是为了促进实现, 其中一个内核的
一系列存储可能已经到达某些内核(它们的值对这些内核的负载可见), 但其他内核则没有。不
变量  2  等同于我们之前描述的类似一致性的定义。 与不变量  2  不同的是, 它是一个安全不变量
(坏事一定不会发生), 不变量  1  是一个活跃不变量(好事最终一定会发生)。

由  Hennessy  和  Patterson  [3]  指定的一致性的另一个定义由三个不变量组成: (1)  一
个内核加载到内存位置  A  获得该内核先前存储到  A  的值, 除非另一个内核已存储到  A  之间;  
(2)  如果  S  和负载“在时间上充分分离” 并且如果  S  和负载之间没有发生其他存储,
则  A  的负载
通过另一个核心获得存储  S  到  A  的值;  (3)  存储到同一内存位置被序列化(与前面定义中的
不变量  2  相同)。

与前面的定义一样,
这组不变量同时捕获了安全性和活性。

2.4.3  什么时候一致性相关?
连贯性的定义 无论我们选择哪个定义 只在特定情况下相关,
架构师必须知道什么时候适用,

么时候不适用。
我们现在讨论两个重要的问题。

‧  一致性适用于保存共享地址空间块的所有存储结构。 这些结构包括  L1  数据缓存、
L2  缓存、

享末级缓存  (LLC)  和主内存。
这些结构还包括  L1  指令缓存和转换后备缓冲区  (TLB)。  2

‧  Coherence对程序员来说不是直接可见的。
相反,
处理器流水线和一致性协议共同强制执行
一致性模型 而且只有一致性模型对程序员可见。

2在某些体系结构中,
TLB  可以保存并非严格为共享内存中块副本的映射。
Machine Translated by Google

16  2.  一致性基础知识

2.5  参考文献
[1]  K.  Gharachorloo。 共享内存多处理器的内存一致性模型。
博士论文,
计算机系统实验室,
斯坦福大学,
1995  年  12  月。  15

[2]  K.  Gharachorloo、
D.  Lenoski、J.  Laudon、
P.  Gibbons、A.  Gupta  和  J.  Hennessy。
可扩展共享内存中的内存一致性和事件排序。 在过程中。 第  17  届年度计算机体系结构国际研讨会,
第  15‑26  页,1990  年  5  月。
DOI:  10.1109/isca.1990.134503。  15

[3]  JL  轩尼诗和  DA  帕特森。 计算机体系结构:
定量方法,
第  4  期
编辑。 摩根考夫曼, 2007  年。
15

[4]  IBM。  Power  ISA  版本  2.06  修订版  B.  http://www.power.org/resources/
下载/PowerISA_V2.06B_V2_PUBLIC.pdf,  2010  年  7  月15  日

[5]  MMK  Martin、
MD  Hill  和  DA  Wood。
令牌一致性: 解耦性能和正确性。 在过程中。 第  30  届计算机体
系结构国际研讨会,  2003  年  6  月。 DOI:
10.1109/isca.2003.1206999。  13
Machine Translated by Google

17

第3章

内存一致性
动机和顺序
一致性
本章深入研究为程序员和实现者定义共享内存系统行为的内存一致性模型(又名内存模型)。

些模型定义了正确性,
以便程序员知道期望什么,实现者知道要提供什么。

我们首先激发定义内存行为的需求(第3.1  节),
说明内存一致性模型应该做什么(第3.2  节),
并比较和对比一致性和连贯性(第3.3  节)。

然后, 我们探索(相对) 直观的顺序一致性  (SC)  模型。  SC  很重要,因为它是许多程序员对


共享内存的期望, 并为理解接下来两章中介绍的更宽松(弱) 的内存一致性模型提供了基础。 我们
首先介绍  SC  的基本思想(第3.4  节) ,并介绍我们也将在后续章节中使用的形式主义(第3.5  
节)。 然后我们讨论  SC  的实现,首先是作为操作模型的简单实现(第  3.6  节), 具有缓存一致性
的  SC  的基本实现(第3.7  节),具有缓存一致性的  SC  的更优化实现(第3.8  节), 以及原子操
作的实现(第3.9  节)。 我们通过提供  MIPS  R10000  案例研究(第3.10  节)
和指向一些进一步
阅读(第3.11  节)来结束我们对  SC  的讨论。

3.1  共享内存行为的问题
要了解为什么必须定义共享内存行为, 请考虑表  3.1  中描述的两个核心  1  的示例执行。  (这个
例子, 和本章所有例子一样, 假定所有变量的初始值为零。) 大多数程序员会期望核心  C2  的寄存
器  r2  应该得到值  NEW。
然而,在当今的某些计算机系统中, r2  可以为  0。

硬件可以通过重新排序核心  C1  的存储  S1  和  S2  来使  r2  获得值  0。
在本地(即, 如果我们
只看  C1  的执行而不考虑与其他线程的交互), 这种重新排序似乎是正确的, 因为  S1  和  S2  访问
不同的地址。 第  18页的边栏
1让“核心”
指代软件对核心的看法,
它可以是实际核心或多线程核心的线程上下文。
Machine Translated by Google

18  3.  记忆一致性动机和顺序一致性
表  3.1:  r2  是否应该始终设置为  NEW?

核心C1 核心C2 评论

S1:
存储数据=新; /*  最初,
data  =  0  &  fl  ag  ≠  SET  */
S2:  存储标志  =  SET; L1:
负载  r1  =  fl  ag; /*  L1  &  B1  可以重复多次  */
B1:
如果(r1≠SET)
转到L1;
L2:
加载r2=数据;

表  3.2:
表3.1中程序的一种可能执行

相干状态 相干状态
循环 核心C1 核心C2
数据 旗帜
1个
S2:
存储标志  =  SET C2  只读 C1  读写
2个
L1:
加载  r1=fl  ag C2  只读 C2  只读
3个 L2:
加载  r2=数据 C2  只读 C2  只读
4个 S1:
存储数据  =  新 C1  读写 C2  只读

描述了硬件可能重新排序内存访问的几种方式,包括这些存储。
非硬件专家的读者可能希
望相信这种重新排序会发生(例如,使用非先进先出的写入缓冲区)。

通过对S1和S2的重新排序,
执行顺序可能是S2、
L1、
L2、
S1,
如表3.2所示。

边栏:
内核如何重新排序内存访问

此边栏描述了现代内核可能重新排序对不同地址的内存访问的几种方式。那些
不熟悉这些硬件概念的人可能希望在第一次阅读时跳过这一点。
现代内核可能会对
许多内存访问进行重新排序,
但足以推理对两个内存操作进行重新排序。在大多数情
况下,
我们只需要推理一个内核将两个内存操作重新排序到两个不同的地址,因为顺
序执行(即冯诺依曼)
模型通常要求对同一地址的操作按原始程序顺序执行。我们根
据重新排序的内存操作是加载还是存储,
将可能的重新排序分为三种情况。

商店到商店重新排序。如果一个内核有一个非  FIFO  写缓冲区,
允许存储以不同
于它们进入的顺序离开,则两个存储可能会被重新排序。 如果第一个存储在缓存中未
命中而第二个命中或者如果第二个存储
Machine Translated by Google

3.1.共享内存行为的问题  19

可以与较早的商店合并(即, 在第一家商店之前)。 请注意, 即使内核按程序顺序执行所有指令, 这些重新排


序也是可能的。 将存储重新排序到不同的内存地址对单线程执行没有影响。 然而,
在表  3.1  的多线程示例中,
重新排序核心  C1  的存储允许核心  C2  在看到存储到数据之前将标志视为  SET。请注意, 即使写入缓冲区排
入完全一致的内存层次结构, 问题也未解决。  Coherence  将使所有缓存不可见,但存储已经重新排序。

加载‑加载重新排序。 现代动态调度内核可能会乱序执行指令。 在表  3.1  的示例中,  Core  C2  可以


乱序执行负载  L1  和  L2。 仅考虑单线程执行, 这种重新排序似乎是安全的, 因为  L1  和  L2  指向不同的地址。
然而, 重新排序  Core  C2  的加载与重新排序  Core  C1  的存储行为相同;
如果内存引用按  L2、 S1、
S2  和  L1  的
顺序执行, 则  r2  被赋值为  0。 如果省略分支语句  B1, 则这种情况更加合理, 因此没有控制依赖性将  L1  和  
L2  分开。

加载‑存储和存储‑加载重新排序。 乱序内核也可能会重新排序来自同一线程的加载和存储(到不同
的地址)。
用较晚的存储重新排序较早的加载(加载存储重新排序) 可能会导致许多不正确的行为, 例如在
释放保护它的锁后加载值(如果存储是解锁操作)。 表3.3中的示例说明了使用较晚的加载对较早的存储进
行重新排序(存储加载重新排序) 的效果。 对核心  C1  的访问  S1  和  L1  以及核心  C2  的访问  S2  和  L2  进行
重新排序会产生违反直觉的结果, 即  r1  和  r2  均为  0。
请注意, 由于通常实现的  FIFO  写入缓冲区中的本地旁
路,
存储加载重新排序也可能出现, 即使使用按程序顺序执行所有指令的核心。

读者可能会假设硬件不应允许其中的部分或全部行为,
但如果不更好地了解允许哪些行为,
就很难
确定硬件可以做什么和不能做什么的列表。

表  3.3:  r1  和  r2  都可以设置为  0  吗?

核心C1 核心C2 评论

S1:  x  =  新的; S2:
y  =  新; /*  最初,
x  =  0  &  y  =  0  */
L1:  r1  =  y; L2:  r2  =  x;

此执行满足相干性,
因为未违反  SWMR  属性,
因此  inco
here并不是这个看似错误的执行结果的根本原因。
Machine Translated by Google

20  3.  记忆一致性动机和顺序一致性
让我们考虑另一个受  Dekker  算法启发的确保互斥的重要示例,
如表  3.3  所示。
执行后,
r1  
和  r2  允许取什么值?直觉上, 人们可能会认为存在三种可能性:

‧  (r1,  r2)  =  (0,  NEW )  用于执行  S1、
L1、
S2,
然后是  L2

‧  (r1,  r2)  =  (NEW,  0)  对于  S2、
L2、
S1  和  L1

‧  (r1,  r2)  =  (NEW,  NEW ),
例如,
对于  S1、
S2、
L1  和  L2

令人惊讶的是,大多数真实硬件, 例如  Intel  和  AMD  的  x86  系统,
也允许  (r1,  r2)  =  (0,  
0),
因为它使用先进先出  (FIFO)  写入缓冲区来提高性能。 与表  3.1  中的示例一样, 所有这些执行
都满足缓存一致性, 甚至  (r1,  r2)  =  (0,  0)。

一些读者可能会反对这个例子,因为它是不确定的(允许有多个输出)并且可能是一个令
人困惑的编程习惯用法。但是,
首先,
所有当前的多处理器在默认情况下都是不确定的; 我们所知
道的所有体系结构都允许并发线程执行的多种可能交错。 确定性的错觉有时(但并非总是)
是由
具有适当同步习惯用法的软件造成的。因此,在定义共享内存行为时,
我们必须考虑非确定性。

此外,内存行为通常是为所有程序的所有执行定义的, 即使是那些不正确的或故意微妙的
(例如,对于非阻塞同步算法)。然而,在第  5  章中,
我们将看到一些允许某些执行具有未定义行
为的高级语言模型,例如,具有数据竞争的程序的执行。

3.2  什么是内存一致性模型?
上一小节中的示例说明共享内存行为是微妙的,为精确定义  (a)  程序员可以期望的行为和  (b)  系
统实现者可以使用的优化提供了价值。内存一致性模型消除了这些问题。

内存一致性模型,或者更简单地说,
内存模型,
是对使用共享内存执行的多线程程序的允
许行为的规范。对于使用特定输入数据执行的多线程程序,
它指定动态加载可能返回的值。

与单线程执行不同, 通常允许多个正确的行为。
通常,内存一致性模型  MC  给出了将执行划分为服从  MC  (MC  执行)
和不服从  MC  (非  
MC  执行)
的规则。 执行的这种划分反过来又划分了实现。  MC  实现是一个只允许  MC  执行的系
统,而非MC  实现有时允许非  MC  执行。

最后,我们一直对编程水平含糊不清。
我们首先假设程序是硬件指令集体系结构中的可执
行文件,并且我们假设
Machine Translated by Google

3.3.一致性对比一致性  21

内存访问是由物理地址标识的内存位置(即, 我们不考虑虚拟内存和地址转换的影响)。
在第5  
章中,
我们将讨论高级语言  (HLL)  的问题。
例如,然后我们将看到,
编译器将变量分配给寄存器可
以以类似于硬件重新排序内存引用的方式影响  HLL  内存模型。

3.3  一致性  VS。
一致性
第2章使用我们在这里非正式重复的两个不变量定义了高速缓存一致性。  SWMR  不变量确保在
任何时候对于具有给定地址的内存位置, 要么  (a)  一个内核可以写入(和读取) 该地址,
要么  (b)  
零个或多个内核只能读取它。  Data  ‑Value  Invariant确保正确传递对内存位置的更新,以便内
存位置的缓存副本始终包含最新版本。

缓存一致性似乎定义了共享内存行为。它不是。
从图  3.1  可以看出,
一致性协议只是为处理
器核心流水线提供了一个内存系统的抽象。它不能单独确定共享内存行为; 这

程序 程序 程序

加载(⋯)
存储(⋯) 负载响应(...)
一致性模型

核 核 核
⋯⋯ ⋯⋯
管道 管道 管道

读请求(⋯)
写请求 读响应(⋯)
写响应
管道 (⋯) (⋯)

一致性

私人的 私人的 私人的


缓存 缓存 缓存

有限责任公司

主存储器

图  3.1:
处理器核心流水线结合一致性协议强制执行一致性模型。
Machine Translated by Google

22  3.  记忆一致性动机和顺序一致性
管道也很重要。 例如,
如果流水线以与程序顺序相反的顺序重新排序并向一致性协议呈现内存操作
即使一致性协议正确地完成了它的工作 共享内存的正确性也可能不会随之而来。

总之:

‧  缓存一致性不等于内存一致性。

‧  内存一致性实现可以将缓存一致性用作有用的“黑匣子”。

3.4  顺序一致性(SC)
的基本思想
可以说最直观的内存一致性模型是  SC。
它首先由  Lam  port  [12]  正式化,如果“执行的结果与操
作按照程序指定的顺序执行的结果相同”, 他将单个处理器(内核) 称为顺序处理器。然后,
他称多
处理器顺序一致,如果“任何执行的结果都相同, 就好像所有处理器(核心) 的操作都按某种顺序
执行,
并且每个处理器(核心)的操作出现在这个序列中它的程序指定的顺序。”

这种总的操作顺序称为内存顺序。
在  SC  中,
内存顺序尊重每个核心的程序顺序,
但其他一致性模型
可能允许内存顺序并不总是尊重程序顺序。

图3.2描述了表  3.1  中示例程序的执行。中间垂直向下的箭头表示内存顺序  (<m), 而每个核
心的向下箭头表示其程序顺序  (<p)。 我们使用运算符  <m  表示内存顺序,
因此  op1  <m

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

L1:
r1  =  标志; /*  0  */

S1:
数据=新的; /*  新的  */
L1:
r1  =  标志; /*  0  */

L1:
r1  =  标志; /*  0  */
S2:
标志=设置; /*  放  */
L1:
r1  =  标志; /*  放  */

L2:  r2  =  数据; /*  新的  */

图  3.2:
表3.1程序的顺序一致执行。
Machine Translated by Google

3.5.  SC  的一点形式主义  23

op2  意味着  op1  在内存顺序上先于  op2。 类似地, 我们使用运算符  <p  来表示给定内核的程序顺序,


因此  op1  <p  op2  意味着  op1  在该内核的程序顺序中位于  op2  之前。 在  SC  下,
内存顺序尊重每个
内核的程序顺序。  “尊重” 意味着op1  <p  op2意味着op1  <m  op2。
注释中的值  (/* ...  */)  给出加载
或存储的值。 该执行以  r2  为  NEW  终止。 更一般地, 表3.1  的程序的所有执行都以  r2  为  NEW  终止。
唯一的不确定性 L1  在加载值  SET  之前多少次将标志加载为  0 并不重要。

这个例子说明了  SC  的价值。 在  3.1  节中, 如果您期望  r2  必须是


NEW, 您可能独立发明了  SC, 尽管不如  Lamport  精确。
SC  的值在图3.3  中进一步揭示, 它说明了表  3.3  中程序的四次执行。 图3.3a–c  描述了对应于三
个直观输出的  SC  执行: (r1,  r2)  =  (0,  NEW),  (NEW,  0),  or  (NEW,  NEW)。
请注意, 图3.3c仅描述了导
致  (r1,  r2)  =  (NEW,  NEW)  的四种可能的  SC  执行中的一种; 这个执行是{S1, S2,L1, L2},
其他是{S1,
S2, L2, L1}, {S2, S1,L1, L2}, 和{S2, S1, L2,
L1}。 因此,在图3.3a‑c  中, 有六次合法的  SC  执行。

图3.3d显示了对应于输出  (r1,  r2)  =  (0,  0)  的非  SC  执行。
对于此输出,
无法创建遵循程序顺序
的内存顺序。 程序顺序规定:

‧  S1  <p  L1

‧  S2  <p  L2

但是内存顺序决定了:

‧  L1  <m  S2(所以  r1  为  0)

‧  L2  <m  S1(所以  r2  为  0)

遵守所有这些约束会导致循环, 这与总顺序不一致。
图3.3d中的额外弧线说明了循环。
我们刚刚看到六次  SC  执行和一次非  SC  执行。
这可以帮助我们理解  SC  实现:
SC  实现必须允
许前六次执行中的一次或多次执行, 但不能允许第七次执行。

3.5  一点  SC  形式主义
在本节中, 我们对  SC  进行了更精确的定义, 特别是为了让我们能够在接下来的两章中将  SC  与较弱的
一致性模型进行比较。 我们采用  Weaver  和  Germond  [20]  的形式主义 一种指定一致性的公理化
方法, 我们将在第11  章详细讨论 使用以下符号: L(a)  和  S(a)  分别表示加载和存储,
解决  a.  Orders  
<p  和  <m  分别定义程序和全局内存顺序。 程序
Machine Translated by Google

24  3.  记忆一致性动机和顺序一致性

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)


S1:  x  =  新的; /*  新的  */

L1:  r1  =  y; /*  0  */
S2:
y  =  新; /*  新的  */

L2:  r2  =  x; /*  新的  */

结果:
(r1,
r2)
=(0,
新)

(a)  SC  执行  1

S2:
y  =  新; /*  新的  */

L2:  r2  =  x; /*  0  */
S1:  x  =  新的; /*  新的  */

L1:  r1  =  y; /*  新的  */
结果:
(r1,  f2)  =  (NEW,  0)

(b)  SC  执行  2

S1:  x  =  新的; /*  新的  */
S2:
y  =  新; /*  新的  */
L1:  r1  =  y; /*  新的  */

L2:  r2  =  x; /*  新的  */

结果:
(r1,
r2)
=(新,
新)

(c)  SC  执行  3
S2:
y  =  新; /*  新的  */

L2:  r2  =  x; /*  0  */
S1:  x  =  新的; /*  新的  */

L1:  r1  =  y; /*  0  */
结果:
(r1,  r2)  =  (0,  0)

(d)  不是  SC  执行

图  3.3:
表3.3程序的四种可选执行。
Machine Translated by Google

3.5.  SC  的一点形式主义  25

order  <p  是每个核心的总顺序,
它捕获每个核心在逻辑上(顺序地)
执行内存操作的顺序。  global  memory  
order  <m  是所有核的内存操作的总顺序。

SC执行需要以下条件。

(1)  所有内核将它们的加载和存储插入到  <m  的顺序中,
这与它们的程序顺序有关,
无论它们是相同还是不同的地址
(即  a=b  或a  ¤  b)。
有四种情况:

‧  If  L(a)  <p  L(b) )  L(a)  <m  L(b) /*  Load!Load  */

‧  如果  L(a)  <p  S(b) )  L(a)  <m  S(b) /*  Load!Store  */

‧  如果  S(a)  <p  S(b) )  S(a)  <m  S(b) /*  Store!Store  */

‧  如果  S(a)  <p  L(b) )  S(a)  <m  L(b) /*  Store!Load  */

(2)  每个加载从它之前的最后一个存储中获取它的值(以全局内存顺序)
到同一个
地址:

L(a)  的值  =  MAX  的值<m  {S(a)  |  S(a)  <m  L(a)},
其中记忆顺序为  MAX。” <米
表示“最新的

我们将在3.9  节中更深入地讨论原子读‑修改‑写  (RMW)  指令,
进一步限制允许的执行。
例如,
测试和设置指令
的每次执行都要求测试的加载和集合的存储在逻辑上连续出现在内存顺序中(即, 相同或不同地址的其他内存操作不
会插入他们)。

我们在表  3.4  中总结了  SC  的排序要求。
该表指定了一致性模型强制执行的程序排序。
例如,
如果一个给定的线
程在程序顺序中有一个在存储之前的加载(即,
加载是表中的“操作  1”,
存储是“操作  2”),
那么这个交叉点的表
条目是一个“X ”
表示这些操作必须按程序顺序执行。
对于  SC,
所有内存操作都必须按程序顺序执行;
在我们将在接下
来的两章中研究的其他一致性模型下,
其中一些排序约束被放宽了(即,
其排序表中的某些条目不包含“X”)。

SC实现仅允许  SC  执行。
严格来说,
这是SC  实现的安全属性(不伤害)。  SC  实现也应该有一些活性属性(做
一些好事)。
具体来说,
商店必须最终对重复尝试加载该位置的负载可见。
此属性称为最终写入传播,
通常由一致性协
议确保。
更一般地说,
避免饥饿和一些公平也很有价值,
但这些问题超出了本次讨论的范围。
Machine Translated by Google

26  3.  记忆一致性动机和顺序一致性
表  3.4:  SC  排序规则。  “X”
表示强制排序。

操作2
加载 存储RMW




加载 X X X
店铺 X X X
最小重量  X X X

3.6  朴素的  SC  实现
SC  允许两个朴素的实现,
这使得更容易理解  SC  允许哪些执行。

多任务单处理器首先, 可以通
过在单个顺序内核(单处理器) 上执行所有线程来为多线程用户级软件实现  SC。 线程  T1  
的指令在核心  C1  上执行,直到上下文切换到线程  T2  等。
在上下文切换时, 任何挂起的内
存操作必须在切换到新线程之前完成。 因为每个线程在其量子中的指令作为一个原子块
执行(并且因为单处理器正确地遵守内存依赖性), 所有  SC  规则都被强制执行。

开关其次,
可以用一组核心  C、
一个开关和内存来实现  SC,
如图3.4  所示。
假设每个内核按照其程序顺
序一次一个地向交换机提供内存操作。 每个核心都可以使用任何不影响它向交换机提供
内存操作的顺序的优化。 例如,
可以使用带有分支预测的简单五级有序流水线。

C1 C2 中⋯ 每个核心  Ci  都试图按照其程序顺
序进行下一次内存访问  <p.

转变 e  switch选择一个core,
让它完成一次内存访
问,
如此往复;
这定义了内存顺序<m。

记忆

图  3.4:
使用内存开关的简单  SC  实现。
Machine Translated by Google

3.7.具有高速缓存一致性的基本  SC  实现  27

接下来假设交换机选择一个核心, 允许内存完全满足加载或存储,并且只要存在请求就重复
此过程。交换机可以通过任何方法(例如, 随机)
挑选核心,该方法不会因准备好的请求而使核心挨
饿。
此实现通过构造在操作上实现了  SC。

评估这些实
施的好消息是它们提供了定义  (1)  允许的  SC  执行和  (2)  SC  实施“黄金标准” 的操作模型。  (在
第11  章中,
我们将看到此类操作模型可用于正式指定一致性模型。) switch  实现还用作  SC  可以
在没有缓存或一致性的情况下实现的存在证明。

当然,
坏消息是这些实现的性能不会随着内核数量的增加而扩展,这是由于在第一种情况下
使用单个内核而在第二种情况下使用单个开关/内存的顺序瓶颈。 这些瓶颈导致一些人错误地认为  
SC  排除了真正的并行执行。
它没有,
正如我们接下来将看到的那样。

3.7  具有高速缓存一致性的基本  SC  实现

高速缓存一致性促进了  SC  实现,这些实现可以完全并行地执行非冲突的加载和存储 如果它们
针对相同的地址并且至少其中一个是存储, 则两个操作会发生冲突。 此外,
创建这样一个系统在概
念上很简单。
在这里,我们主要将一致性视为实现第  2  章的  SWMR  不变量的黑盒。我们通过稍微打开一
致性块框以显示简单的一级  (L1)  缓存来提供一些实现直觉:


使用状态修改(M) 表示一个内核可以写入和读取的L1块,

使用状态共享  (S)表示一个或多个内核只能读取的  L1  块,
并且

‧  GetM和GetS分别表示获取M  和S  中块的一致性请求。
正如第6章及以后讨论的那样,
我们不需要深入了解一致性是如何实现的。

图3.5a描述了图3.4的模型,
其中开关和内存被表示为黑盒的高速缓存一致性内存系统所取
代。
每个内核按照其程序顺序一次一个地向高速缓存一致性内存系统呈现内存操作。 内存系统在开
始对同一内核的下一个请求之前完全满足每个请求。

图3.5b稍微“打开”内存系统黑框,
显示每个核心都连接到自己的  L1  缓存(稍后我们将讨
论多线程)。 如果内存系统具有具有适当一致性权限的  B(状态  M  或  S  用于加载,
M  用于存储),
则内存系统可以响应加载或存储到块  B。此外,
内存系统可以响应来自不同的请求
Machine Translated by Google

28  3.  记忆一致性动机和顺序一致性
每个核心  Ci  都试图按照其程序顺
序进行下一次内存访问  <p.

e内存系统在逻辑上选择一个核心,

它完成一次内存访问,然后重复;
这定义
了内存顺序<m。

(a)  黑盒记忆系统

同上

同上,
但内核可以同时完成对具有足够  (L1)  高速缓
存一致性权限的块的访问,因为此类访问必须是非
冲突的(对不同的块或所有负载)并且可以以任何
逻辑顺序放入内存顺序。

(b)  暴露了  L1  缓存的内存系统

图  3.5:
实现缓存一致性的  SC。

并行内核, 前提是相应的  L1  缓存具有适当的权限。
例如,图3.6a描述了四个内核各自寻求执行内存操作之前的缓存状态。 这四个操作并不冲
突,
都可以由各自的L1缓存来满足, 因此可以并发进行。 如图3.6b  所示,
我们可以任意排序
这些操作以获得合法的  SC  执行模型。 更一般地说,
L1  缓存可以满足的操作总是可以并发
完成,因为一致性的  SWMR  不变性确保它们不冲突。

评估
我们创建了一个  SC  实现:
‧  充分利用缓存的延迟和带宽优势,
‧  与其使用的高速缓存一致性协议一样具有可扩展性,
并且

‧  将实施核心的复杂性与实施一致性分离开来。
Machine Translated by Google

3.8.具有高速缓存一致性的优化  SC  实现  29

(a)  四个访问并发执行

(b)  在  SC  执行中逻辑排序的四个访问(一种可能的排序)

图  3.6:
具有缓存一致性的并发  SC  执行。

3.8  带缓存的优化  SC  实现
一致性

大多数真正的核心实现比我们具有缓存一致性的基本  SC  实现更复杂。
核心采用预取、 推
测执行和多线程等功能来提高性能和容忍内存访问延迟。 这些功能与内存接口交互, 我们
现在讨论这些功能如何影响  SC  的实现。
值得牢记的是,
只要不产生违反  SC  的最终结果
(加载返回的值),
任何功能或优化都是合法的。

非绑定预取块  B  的非绑
定预取是对一致性内存系统的请求, 以更改  B  在一个或多个高速缓存中的一致性状态。
最常见的是,
软件、 核心硬件或高速缓存硬件请求预取以更改  B  在一级高速缓存中的状
态以允许加载(例如, B  的状态为  M  或  S)
或加载和存储(B  的状态为  M)
发布一致性请
求,
例如
Machine Translated by Google

30  3.  记忆一致性动机和顺序一致性
作为  GetS  和  GetM。
重要的是,
在任何情况下,非绑定预取都不会改变块  B  中寄存器或数据
的状态。 非绑定预取的效果仅限于图  3.5a  的“高速缓存一致性内存系统” 块内, 使得非绑定
预取对内存一致性模型的影响相当于无操作的功能。 只要加载和存储按程序顺序执行, 以什么
顺序获得一致性权限并不重要。

实现可以在不影响内存一致性模型的情况下进行非绑定预取。
这对于内部高速缓存预
取(例如,流缓冲区)
和更积极的内核都很有用。

推测内核考虑一
个内核,它按程序顺序执行指令, 但也进行分支预测,其中后续指令(包括加载和存储) 开始
执行,
但可能因分支预测错误而被压缩(即, 使其效果无效)。 可以使这些压缩的加载和存储
看起来像非绑定预取, 从而使这种推测是正确的,因为它对  SC  没有影响。 分支预测后的加载
可以提交给  L1  缓存,其中它要么未命中(导致非绑定  GetS  预取)
要么命中, 然后将值返回到
寄存器。如果负载被压扁, 内核会丢弃寄存器更新,
从而消除负载的任何功能影响 就好像它
从未发生过一样。 缓存不会撤消非绑定预取,因为这样做不是必需的, 如果重新执行加载,预取
块可以帮助提高性能。 对于存储,核心可能会提前发出一个非绑定的  GetM  预取, 但它不会将
存储提交给缓存, 直到存储被保证提交。

闪回测验问题  1:
在保持顺序一致性的系统中,
内核必须按程序顺序发出一致性请求。
对或
错?
答:
假的!核心可以以任何顺序发出一致性请求。

动态调度的内核许多现代内
核动态地安排指令执行超出程序顺序, 以实现比静态调度的内核更高的性能, 静态调度的内核
必须按照严格的程序顺序执行指令。 使用动态或乱序(程序)调度的单核处理器必须简单地在
程序中强制执行真正的数据依赖性。 然而,在多核处理器的上下文中, 动态调度引入了一个新
问题:内存一致性推测。 考虑一个内核, 它希望动态地重新排序两个加载  L1  和  L2  的执行(例
如,
因为  L2  的地址在  L1  的地址之前计算)。许多内核会推测在  L1  之前执行  L2, 并且它们预
测这种重新排序对其他内核不可见, 这将违反  SC。

推测  SC  需要核心验证预测是否正确。  Gharachorloo  等人。  [8]介绍了执行此检查
的两种技术。 一、 后核推测
Machine Translated by Google

3.8.具有高速缓存一致性的优化  SC  实现  31

执行  L2,
但在提交  L2  之前,
核心可以检查推测访问的块是否没有离开缓存。
只要块保留在
缓存中, 它的值就不会在加载执行和提交之间发生变化。 为执行此检查,
内核跟踪  L2  加载的
地址并将其与逐出的块和传入的一致性请求进行比较。

传入的  GetM  表示另一个内核可能会乱序观察  L2,
而此  GetM  将暗示错误推测并压制推测
执行。
第二种检查技术是在内核准备好提交负载时重放每个推测负载2  [2,  17]。 如果提交时
加载的值不等于之前推测加载的值, 则预测不正确。 在该示例中, 如果  L2  的重放加载值与  
L2  的原始加载值不同, 则加载‑加载重新排序会导致明显不同的执行, 并且必须压制推测执
行。

动态调度内核中的非绑定预取动态调度内核很可能会遇
到程序顺序加载和存储未命中的情况。
例如,假设程序顺序是加载  A、 存储  B,
然后是存储  C。 内核可能会“无序” 地启动非绑定预
取,
例如, 首先并行执行  GetM  C,
然后是  GetS  A  和  GetM  B。  SC  不受非绑定预取顺序的
影响。  SC  只要求核心的加载和存储(看起来) 按程序顺序访问其一级缓存。  Coherence  
要求一级缓存块处于适当的状态以接收加载和存储。

重要的是,
SC(或任何其他内存一致性模型):
‧  指示加载和存储(似乎)
应用于相干内存的顺序

‧  不规定一致性活动的顺序。

闪回测验问题  2:
内存一致性模型指定一致性事务的合法顺序。
对或错?

答:
假的!

多线程SC  实现
可以适应多线程 粗粒度、 细粒度或同步。 每个多线程核心在逻辑上应该等同于多个(虚
拟)
核心,通过一个开关共享每个一级缓存, 缓存选择下一个要服务的虚拟核心。 此外,
每个
缓存实际上可以并发地服务于多个不冲突的请求, 因为它可以假装它们是按某种顺序服务
的。一个挑战是确保线程  T1  在存储对其他内核上的线程“可见” 之前无法读取同一内核上
另一个线程  T2  写入的值。
因此,虽然线程  T1  可以读取

2Roth  [17]演示了一种通过确定何时不需要重放来避免许多负载重放的方案。
Machine Translated by Google

32  3.  记忆一致性动机和顺序一致性
一旦线程  T2  按内存顺序插入存储(例如,
通过将其写入状态  M  中的高速缓存块),
它就无法从
处理器内核中的共享加载存储队列中读取该值。

边栏:
高级  SC  优化此边栏描述了一些高
级  SC  优化。
退休后的猜测。 单核处理器通常采用称为写(存储) 缓冲区的结构来隐藏存储未命中的
延迟; 存储从处理器管道退出到写缓冲区, 从那里它从关键路径排入缓存/内存系统。 这在单
核上是安全的, 只要加载检查写入缓冲区是否有未完成的存储到同一地址。 然而,在多核上,
SC  排序规则排除了写缓冲区的简单使用。 动态调度的内核可以隐藏部分(但不是全部) 存储
未命中延迟。 为了隐藏更多的存储未命中延迟, 已经有许多提议积极实施  SC,
利用指令窗口之
外的推测。 关键思想是推测性地退出未决存储未命中的加载和存储, 同时以细粒度[9,  16]或粗
粒度块[1,  3,  11,  19]  分别维护推测性退出指令的状态。

非推测性重新排序。
甚至可以在强制执行  SC  的同时非推测性地乱序执行内存操作,
只要重新排序对其他内核不可见[7、
18 ]。
在没有回滚恢复的情况下, 您如何确保其他核心不
可见重新排序?

一种方法(称为一致性延迟) 涉及延迟一致性请求:
具体来说, 当较新的内存操作通过
未决的较旧的操作退出时, 对较年轻的位置的一致性请求被延迟,直到较旧的内存操作退出。
一致性延迟存在固有的死锁风险, 需要谨慎的死锁避免机制。在表  3.3  所示的示例中,
如果负
载  L1  和  L2  都退出了存储并且对各自位置的一致性请求被延迟,这可能会阻止存储中的任
何一个完成, 从而导致死锁。

另一种方法(称为前置序列化)要求较旧的内存操作完成足够的工作 通常是在一个
中心点进行序列化 以确保较新的操作安全地完成它。 冲突排序[6]允许加载和存储在挂起
存储未命中后退出,只要挂起存储在目录中序列化并确定挂起存储的全局列表; 只要年轻的内
存操作不与这个链表冲突,它就可以安全退役。  Gope  和  Lipasti  [4]提出了一种为有序处理
器量身定制的方法,其中每个加载或存储都按程序顺序从目录中获取互斥锁, 但可以乱序退
出。

最后,
可以利用编译器或内存管理单元的帮助来确定可以安全地重新排序的访问[5]。

如,
可以安全地重新排序对线程私有或只读变量的两次访问。
Machine Translated by Google

3.9.  SC  33  的原子操作

3.9  SC  的原子操作
要编写多线程代码, 程序员需要能够同步线程, 而这种同步通常涉及原子地执行成对的操作。 此功能由原子执行
“读‑修改‑写”
(RMW)  的指令提供,例如众所周知的“测试和设置”、 “获取和递增” 以及“比较和‑交换。” 这些
原子指令对于正确同步至关重要, 并用于实现自旋锁和其他同步原语。 对于自旋锁, 程序员可能会使用  RMW  原子
地读取锁的值是否已解锁(例如, 等于  0)
并写入锁定值(例如,等于  1)。
为了使  RMW  成为原子的,RMW  的读(加
载)
和写(存储) 操作必须连续出现在  SC  要求的总操作顺序中。

在微体系结构中实现原子指令在概念上很简单,但天真的设计会导致原子指令性能不佳。实现原子指令的
正确但简单的方法是让核心有效地锁定内存系统(即防止其他核心发出内存访问)并执行其对内存的读取、修改和
写入操作。这种实现虽然正确且直观,
但却牺牲了性能。

更积极的  RMW  实现利用了  SC  只需要所有请求的总顺序的外观的洞察力。因此,
原子  RMW  可以通过首先
让核心在其高速缓存中获取处于状态  M  的块来实现, 如果该块尚未处于该状态。
然后核心只需要在其缓存中加载
和存储块 无需任何一致性消息或总线锁定 只要它等待服务任何传入的块一致性请求, 直到存储之后。 这种等
待不会有死锁的风险, 因为存储可以保证完成。

闪回测验问题  3:
要执行原子读‑修改‑写指令(例如,
测试和设置),
一个内核必须始终与其他内核通信。
对或
错?
答:
假的!

更优化的  RMW  实现可以在加载部分和存储部分执行之间留出更多时间, 而不会违反原子性。
考虑块在缓
存中处于只读状态的情况。  RMW  的加载部分可以推测性地立即执行, 而缓存控制器发出一致性请求以将块的状
态升级为读写。当块随后以读写状态获得时, RMW  的写入部分将执行。只要核心能够保持原子性的错觉,这个实现
就是正确的。
为了检查是否保持了原子性的幻觉, 核心必须检查加载块是否从加载部分和存储部分之间的缓存中逐
出;
这种推测支持与  SC  中检测错误推测所需的支持相同(第3.8  节)。
Machine Translated by Google

34  3.  内存一致性动机和顺序一致性3.10  综合起来:
MIPS  R10000

MIPS  R10000  [21]为推测性微处理器提供了一个古老但干净的商业示例,
该微处理器与高速缓存
一致的存储器层次结构合作实现  SC。
在此, 我们专注于  R10000  与实现内存一致性相关的方面。

R10000  是具有分支预测和乱序执行功能的四路超标量  RISC  处理器内核。
该芯片支持  L1  指
令和  L1  数据的回写缓存, 以及到(片外)统一  L2  缓存的专用接口。

如图3.7所示(改编自  Yeager  [21]  中的图  1) ,
芯片的主系统接口总线支持最多四个处理器
的高速缓存一致性。 为了构建具有更多处理器的基于  R10000  的系统, 例如  SGI  Origin  2000(在
第  8.8.1  节中详细讨论),架构师实施了一个目录一致性协议, 该协议通过系统接口总线和专用集线
器芯片连接  R10000  处理器。在这两种情况下, R10000  处理器内核都会看到一个一致的内存系统,
该系统恰好部分在片上, 部分在片外。

在执行过程中,R10000  核心问题(推测)
按程序顺序加载并存储到地址队列中。 加载从它之
前的最后一个存储中获取一个(推测的) 值到相同的地址,如果没有, 则从数据缓存中获取。加载和存
储按程序顺序提交,然后删除它们的地址队列条目。 要提交存储,L1  缓存必须将块保存在状态  M  中,
并且存储的值必须与提交一起自动写入。

重要的是, 缓存块的逐出(由于一致性失效或为另一个块腾出空间)在地址队列中包含加载地
址会压缩加载和所有后续指令, 然后重新执行。因此,
当加载最终提交时,加载的块在执行和提交之间
一直在缓存中, 因此它必须获得与提交时执行时相同的值。因为存储实际上是在提交时写入缓存, 所
以  R10000  在逻辑上按程序顺序将加载和存储呈现给相干内存系统,从而实现  SC,
如前所述。

MIPS MIPS MIPS MIPS


10000  兰特 10000  兰特 10000  兰特 10000  兰特

相干  MESI  总线

图  3.7:
相干  MESI  总线最多可连接四个  MIPS  R10000  处理器。
Machine Translated by Google

3.11.关于  SC  35  的进一步阅读

3.11  有关  SC  的进一步阅读

下面我们重点介绍围绕  SC  的大量文献中的几篇论文。
Lamport  [12]定义了  SC。
据我们所知,
Meixner  和  Sorin  [15]是第一个证明内核按程序顺序向
高速缓存一致性内存系统呈现加载和存储的系统足以实现  SC, 即使这个结果被直觉认为是

一些时间。

SC  可以与数据库可串行化进行比较[10]。 这两个概念的相似之处在于它们都坚持所有实体的
操作似乎以串行顺序影响共享状态。
由于操作和共享状态的性质和期望, 这些概念有所不同。 对于  SC,
每个操作都是对假定不会失败的易
失性状态(内存) 的单次内存访问。 通过串行化, 每个操作都是数据库上的一个事务, 可以读取和写入
多个数据库实体, 并且应该遵守  ACID  属性:
原子性 全有或全无, 即使发生故障;
一致 保持数据库
一致;隔离 不受并发事务的影响; 耐用 效果经得起碰撞和断电。

我们遵循  Lamport  和  SPARC  来定义所有内存访问的总顺序。 虽然这可以减轻某些人的直觉,
但没有必要。 回想一下, 如果来自不同线程的两个访问冲突, 访问相同的位置,并且至少有一个是存储
(或  RMW )。
正如  Shasha  和  Snir  [18]所开创的那样,
可以只定义对冲突访问的约束并让非冲突访
问保持无序, 而不是完全有序。 该视图对于第  5  章的松弛模型特别有价值。

最后, 一个警示故事。 我们之前说过(第3.7  节) ,检查推测执行的负载是否可能被乱序观察的


一种方法是记住负载推测读取的值  A, 如果在提交时提交内存位置, 则提交负载具有相同的价值  A.  
Martin  等人。  [14]表明,
执行值预测  [13]  的核心并非如此。通过值预测,当负载执行时,
内核可以推
测其值。

考虑一个核心推测块  X  的负载将产生值  A, 尽管该值实际上是  B。 在核心推测  X  的负载和它在提交时


重播负载之间, 另一个核心将块  X  的值更改为A.  内核然后在提交时重播负载, 比较两个相等的值, 并错
误地确定推测是正确的。 如果以这种方式进行推测, 系统可能会违反  SC。 这种情况类似于所谓的  ABA  
问题(http://en.wikipedia.org/wiki/ABA_problem),
而  Martin  等人。
表明有一些方法可以在存在
值预测的情况下检查推测, 从而避免违反一致性的可能性(例如, 还可以根据最初推测的负载重放所
有负载)。 本次讨论的重点不是深入研究这个特殊案例或其解决方案的细节, 而是说服您证明您的实
施是正确的, 而不是依赖直觉。
Machine Translated by Google

36  3.  记忆一致性动机和顺序一致性
3.12  参考文献
[1]  C.  Blundell、
MMK  Martin  和  TF  Wenisch。  InvisiFence:
传统多处理器中性能透明的内存排
序。 在过程中。 第  36  届年度计算机体系结构国际研讨会,  2009  年  6  月。 DOI:
10.1145/1555754.1555785。  32

[2]  HW  Cain  和  MH  Lipasti。
内存排序:一种基于价值的方法。 在过程中。 第  31  届年度计算机体系
结构国际研讨会,  2004  年  6  月。 DOI:  10.1109/isca.2004.1310766。  31

[3]  L.  Ceze、J.  Tuck、
P.  Montesinos  和  J.  Torrellas。  BulkSC:
顺序一致性的批量执行。 在过程
中。 第  34  届年度计算机体系结构国际研讨会,  2007  年  6  月。 DOI:
10.1145/1250662.1250697。  
32

[4]  D.  Gope  和  MH  Lipasti。
用于简单有序处理器的  Atomic  SC。 在第  20  届IEEE  高性能计算机体
系结构国际研讨会上,  2014  年。 DOI:  10.1109/hpca.2014.6835950。  32

[5]  A.  Singh、S.  Narayanasamy、
D.  Marino、
TD  Millstein  和  M.  Musuvathi。
端到端的顺序一致
性。 在第  39  届计算机体系结构国际研讨会上,  2012  年。
DOI:
10.1109/isca.2012.6237045。  32

[6]  C.  Lin、
V.  Nagarajan、
R.  Gupta  和  B.  Rajaram。
通过冲突排序实现高效的顺序一致性。 在过
程中。 第  17  届编程语言和操作系统架构支持国际会议  ASPLOS,  2012  年。 DOI:  
10.1145/2150976.2151006。  32

[7]  K.  Gharachorloo、
SV  Adve、
A.  Gupta、
J.  Hennessy  和  MD  Hill。
指定内存一致性模型的系统
要求。 技术报告  CSL‑TR93‑594, 斯坦福大学, 1993  年  12  月。
32

[8]  K.  Gharachorloo、
A.  Gupta  和  J.  Hennessy。 两种增强内存一致性模型性能的技术。在过程
中。 并行处理国际会议, 第一卷。  I, 第  355‑64  页,
1991  年  8  月。
30

[9]  C.  Guiady、
B.  Falsafi  和  T.  Vijaykumar。 是  SC  C  ILP  D  RC  吗?
在过程中。第  26  届计算机体系
结构国际研讨会, 第  162‑71  页, 1999  年  5  月。DOI:  10.1109/isca.1999.765948。  32

[10]  J.  Gray  和  A.  Reuter。
事务处理:
概念和技术。
摩根·考夫曼
出版社,
1993  年。
35

[11]  L.哈蒙德等人。 事务内存的一致性和一致性。 在过程中。 第  31  届年度计算机体系结构国际研讨


会,  2004  年  6  月。
DOI:  10.1109/isca.2004.1310767。  32
Machine Translated by Google

3.12.参考文献  37

[12]  L.  兰波特。如何制作能够正确执行多进程程序的多处理器计算机。  IEEE  计算机交易,  
C‑28(9):
690–91,
1979  年  9  月。
DOI:  10.1109/tc.1979.1675439。  22,  35

[13]  MH  Lipasti  和  JP  Shen。
通过值预测超出数据流限制。 在过程中。 第  29  届年度  IEEE/ACM  国际
微架构研讨会, 第  226–37  页,
1996  年  12  月。
DOI:
10.1109/micro.1996.566464。  35

[14]  MMK  Martin、 DJ  Sorin、
HW  Cain、MD  Hill  和  MH  Lipasti。
在支持多线程或多处理的微处理器
中正确实现值预测。 在过程中。 第  34  届  IEEE/ACM  国际微架构年度研讨会, 第  328–37  页,
2001  年  12  月。DOI: 10.1109/micro.2001.991130。  35

[15]  A.  Meixner  和  DJ  Sorin。
缓存一致性多线程计算机体系结构中内存一致性的动态验证。 在过
程中。 可靠系统和网络国际会议, 第  73‑82  页,
2006  年  6  月。
DOI:
10.1109/dsn.2006.29。  35

[16]  P.  Ranganathan、 VS  Pai  和  SV  Adve。
使用推测退休和更大的指令窗口来缩小内存一致性模
型之间的性能差距。
在过程中。 第  9  届  ACM  并行算法和架构研讨会, 第  199–210  页,
1997  年  6  月。
DOI:
10.1145/258492.258512。  32

[17]  A.罗斯。
存储漏洞窗口  (SVW):
用于增强负载优化的重新执行过滤。 在过程中。 第  32  届年度计算
机体系结构国际研讨会,  2005  年  6  月。
DOI:
10.1109/isca.2005.48。  31

[18]  D.  Shasha  和  M.  Snir。
高效且正确地执行共享内存的并行程序。  ACM  编程语言和系统汇刊,  
10(2):282–312, 1988  年  4  月。
DOI:
10.1145/42190.42277。  32,  35

[19]  TF  Wenisch、
A.  Ailamaki、
A.  Moshovos  和  B.  Falsafi。
免存储等待多处理器的机制。 在过程中。
第  34  届年度国际计算机体系结构研讨会,  2007  年  6  月。 DOI:
10.1145/1250662.1250696。  
32

[20]  DL  Weaver  和  T.  Germond,
Eds。  SPARC  体系结构手册(第  9  版)。  PTR学徒
霍尔,
1994  年。
23

[21]  KC  耶格尔。  MIPS  R10000  超标量微处理器。  IEEE  Micro,  16(2):28–40,
1996  年  4  月。
DOI: 10.1109/40.491460。  34
Machine Translated by Google
Machine Translated by Google

39

第  4  章

Total  Store  Order  和  x86
内存模型
一种广泛实施的内存一致性模型是总存储顺序  (TSO)。  TSO  最先由  SPARC  引入, 更重要的
是, 它似乎与广泛使用的  x86  架构的内存一致性模型相匹配。  RISC‑V  还支持  TSO  扩展  RVTSO,
部分是为了帮助移植最初为  x86  或  SPARC  架构编写的代码。 本章使用类似于前一章关于顺序一
致性的模式来介绍这个重要的一致性模型。 我们首先通过指出  SC  的局限性来部分激励  TSO/
x86(第4.1  节) 。 然后, 在更正式地描述  TSO/x86(第  4.3  节)
之前,我们先直观地介绍  TSO/
x86(第  4.2  节),解释系统如何实现  TSO/x86,包括原子指令和用于强制指令之间排序的指令
(第  4.4  节)。 最后,我们讨论其他资源以了解更多关于  TSO/x86  的信息(第4.5  节) 并比较  
TSO/x86  和  SC(第4.6  节)。

4.1  TSO/X86  的动机

处理器内核长期以来一直使用写入(存储) 缓冲区来保存已提交(退役) 的存储, 直到内存系统的


其余部分可以处理这些存储。 一个store在store  commit时进入write  buffer,
一个store在待写
入的block在cache中处于读写一致状态时退出write  buffer。 值得注意的是, 在缓存获得要写入
的块的读写一致性权限之前, 存储可以进入写入缓冲区; 因此, 写入缓冲区隐藏了服务存储未命中
的延迟。 因为商店很常见, 所以能够避免在大多数商店停滞是一个重要的好处。 此外,
不停止核心
似乎是明智的, 因为核心不需要任何东西, 因为存储寻求更新内存而不是核心状态。

对于单核处理器, 通过确保对地址  A  的加载将最近存储的值返回到  A,
即使写入缓冲区中
有一个或多个  A  的存储,
也可以使写入缓冲区在体系结构上不可见。 这通常是通过将最近存储到  
A  的值旁路到从  A  加载来完成的,
其中“最近”
由程序顺序确定,
或者如果  A  的存储在写入缓冲区
中则停止加载  A .

在构建多核处理器时,
使用多个内核似乎很自然,
每个内核都有自己的旁路写缓冲区,
并假
设写缓冲区在架构上仍然是不可见的。
Machine Translated by Google

40  4.  总存储顺序和  x86  内存模型

表  4.1:  r1  和  r2  都可以设置为  0  吗?

核心C1 核心C2 评论

S1:  x  =  新的; S2:
y  =  新; /*  最初,
x  =  0  &  y  =  0  */
L2:  r1  =  y; L2:  r2  =  x;

这个假设是错误的。
考虑表4.1中的示例代码(与上一章中的表3.3相同)。
假设一个具有有序内核的多核处理
器,
其中每个内核都有一个单项写入缓冲区,
并按以下顺序执行代码。

‧  核心C1  执行存储S1,
但将新存储的NEW  值缓存在其写缓冲区中。

‧  同样,
核心C2  执行存储S2  并将新存储的NEW  值保存在其写入中
缓冲。

‧  接下来,
两个核心执行各自的负载,
L1  和  L2,
并获得旧值
0。

‧  最后,
两个内核的写入缓冲区使用新存储的值NEW  更新内存。

最终结果是  (r1,  r2)  =  (0,  0)。
正如我们在上一章看到的,
这是一个被SC禁止的执行结果。
没有写缓冲区,
硬件
是  SC,
但有写缓冲区,
它不是,
使写缓冲区在多核处理器中在体系结构上可见。

对写缓冲区可见的一种回应是将其关闭,
但由于潜在的性能影响,
供应商一直不愿意这样做。
另一种选择是使
用积极的、
推测性的  SC  实现,
使写入缓冲区再次不可见,
但这样做会增加复杂性,
并且会浪费能量来检测违规和处理
错误推测。

SPARC  和后来的  x86  选择的选项是放弃  SC,
转而采用内存一致性模型,
该模型允许在每个内核上直接使用
先进先出  (FIFO)  写入缓冲区。
新模型  TSO  允许结果“(r1,  r2)  =  (0,  0)”。
这个模型让一些人感到惊讶,
但事实证明,
对于大多数编程习惯用法来说, 它的行为类似于  SC, 并且在所有情况下都得到了很好的定义。

4.2  TSO/X86的基本思想

随着执行的进行,
SC  要求每个内核为连续操作的所有四种组合保留其加载和存储的程序顺序:

‧  加载!
加载

‧  加载!
店铺

‧  太棒了!
大的
Machine Translated by Google

4.2.  TSO/X86  的基本思想  41

‧  商店!  Load /*  包含在  SC  中,
但在  TSO  中省略  */

TSO  包括前三个约束但不包括第四个。
对于大多数程序而言,
此遗漏无关紧要。
表4.2重复了
上一章表3.1的示例程序。
在这种情况下, TSO  允许与  SC  相同的执行,
因为  TSO  保留了核心  C1  的两个存储和核心  C2  的两
个(或更多) 加载的顺序。 图4.1  (同上一章的图3.2 )说明了该程序的执行过程。

表  4.2:  r2  是否应该始终设置为  NEW?

核心C1 核心C2 评论

S1:
存储数据=新; /*  最初,
data  =  0  &  fl  ag  ≠  SET  */
S2:  存储标志  =  SET; L1:
负载  r1  =  fl  ag /*  L1  &  B1  可以重复多次  */
B1:
如果(r1≠SET)
转到L1;
L2:
加载r2=数据;

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

L1:
r1  =  标志; /*  0  */

S1:
数据=新的; /*  新的  */
L1:
r1  =  标志; /*  0  */

L1:
r1  =  标志; /*  0  */
S2:
标志=设置; /*  放  */
L1:
r1  =  标志; /*  放  */

L2:  r2  =  数据; /*  新的  */

图  4.1:
表4.2程序的  TSO  执行。

更一般地说,
对于以下常见编程习惯用法,
TSO  的行为与  SC  相同:

‧  C1  加载并存储到内存位置D1, 。 . .,  Dn  (通常是数据),

‧  C1  存储到  F(通常是同步标志)
以指示上述工作完成,
Machine Translated by Google

42  4.  总存储顺序和  x86  内存模型

‧  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  确保  Ci  在  FENCE  之前的内存
操作(按程序顺序) 在  FENCE  之后的  Ci  内存操作之前按内存顺序放置。使用  TSO  的程序员很少
使用  FENCE(又名内存屏障), 因为  TSO  对大多数程序“做正确的事”。尽管如此, FENCE  对下
一章讨论的宽松模型起着重要作用。

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:  r1  或  r3  可以设置为  0  吗?

核心C1 核心C2 评论

S1:  x  =  新的; S2:
y  =  新; /*  最初,
x  =  0  &  y  =  0  */
L1:  r1  =  x; L3:
r3  =  y;
L2:  r2  =  y; L4:  r4  =  x; /*  假设  r2  =  0  &  r4  =  0  */

4.3  一点  TSO/X86  形式主义

在本节中,
我们使用仅对第  3.5  节的  SC  定义进行三处更改的定义来更准确地定义  TSO 。
Machine Translated by Google

4.3.一点  TSO/X86  形式主义  43

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

S1:  x  =  新的; /*  新的  */
S2:
y  =  新; /*  新的  */
L1:  r1  =  y; /*  新的  */

L2:  r2  =  x; /*  新的  */

结果:
(r1,
r2)
=(新,
新)

(a)  TSO  和  SC  执行  1
S1:  x  =  新的; /*  新的  */

L1:  r1  =  y; /*  0  */
S2:
y  =  新; /*  新的  */

L2:  r2  =  x; /*  新的  */

结果:
(r1,
r2)
=(0,
新)

(b)  TSO  和  SC  执行  2

S2:
y  =  新; /*  新的  */

L2:  r2  =  x; /*  0*/
S1:  x  =  新的; /*  新的  */

L1:  r1  =  y; /*  新的  */
结果:
(r1,  r2)  =  (NEW,  0)

(c)  TSO  和  SC  执行  3

S1:  x  =  新的; /*  新的  */
S2:
y  =  新; /*  新的  */
L1:  r1  =  y; /*  0  */
L2:  r2  =  x; /*  0  */

结果:
(r1,  r2)  =  (0,  0)

(d)  TSO  执行,
但不是  SC  执行

图  4.2:
表4.1程序的四种可选  TSO  执行。
Machine Translated by Google

44  4.  总存储顺序和  x86  内存模型

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

S1:  x  =  新的; /*  新的  */
S2:
y  =  新; /*  新的  */
L1:  r1  =  x; /*  新的  */
旁路旁路 L3:
r3  =  y; /*  新的  */
L2:  r2  =  y; /*  0  */ L4:  r4  =  x; /*  0  */

结果: (r2,  r4)  =  (0,  0)  和  (r1,  
r3)  =  (NEW,  NEW)

图  4.3:
表4.3程序的  TSO  执行(带有“旁路”)。

TSO执行需要以下条件。

1.  所有内核将它们的加载和存储插入内存顺序  <m  尊重它们的程序顺序,
无论它们是相同还是不同的地址(即  
a==b  或  a!=b)。
有四种情况:

‧  如果  L(a)  <p  L(b) )  L(a)  <m  L(b) /*  加载!
加载  */  ‧  如果  
L(a)  <p  S(b) )  L(a)  <m  S(b) /*  加载!
存储  */  ‧  如果  S(a)  <p  
S(b) )  S(a)  <m  S(b) /*  存储!  Store  */  ‧  If  S(a)  <pL(b))S(a)  
<m  L(b)/*  Store!Load  */ /*  更改  1:
启用  FIFOWrite
缓冲  */

2.  每个加载都从它之前的最后一个存储中获取它的值到相同的地址:

‧  L(a)  的值  =  MAX  的值<m  {S(a)  |  S(a)  <m  L(a)} /*  变化2:
需要
旁路  */  ‧  L(a)  
的值  =  MAX  的值<m  {S(a)  |  S(a)  <m  L(a)或  S(a)  <p  L(a)}

最后这个令人费解的等式表示, 加载的值是最后存储到同一地址的值, 该地址要么是  (a)  在内存顺序中它


之前,
要么  (b)  在它之前以程序顺序(但可能在它之后内存顺序),
选项  (b)  优先(即写缓冲区旁路覆盖内存系
统的其余部分)。

3.  第  (1)  部分必须扩充以定义  FENCE: /*  更改  3:
FENCE  Order  Everything
*/

‧  如果L(a)  <p  FENCE )  L(a)  <m  FENCE /*  加载!
栅栏  */

‧  如果S(a)  <p  FENCE )  S(a)  <m  FENCE /*  存储!
栅栏  */

‧  如果FENCE  <p  FENCE )  FENCE  <m  FENCE /*  FENCE !栅栏  */
Machine Translated by Google

4.3.一点  TSO/X86  形式主义  45

‧  If  FENCE  <p  L(a) )  FENCE  <m  L(a) /*  FENCE !加载  */

‧  If  FENCE  <p  S(a) )  FENCE  <m  S(a) /*  FENCE !店铺  */

因为  TSO  已经需要除  Store  以外的所有内容!
加载顺序,
也可以将  TSO  FENCE  定义为唯一
顺序:

‧  如果S(a)  <p  FENCE )  S(a)  <m  FENCE /*  存储!
栅栏  */

‧  If  FENCE  <p  L(a) )  FENCE  <m  L(a) /*  FENCE !加载  */

我们选择让  TSO  FENCE  对所有内容进行冗余排序,
因为这样做不会造成伤害,
并且使它们类
似于我们在下一章中为更宽松的模型定义的  FENCE。

我们在表  4.4  中总结了  TSO  的排序规则。
该表与  SC  的类似表(表3.4)
有两个重要区别。

先,如果操作  #1  是存储而操作  #2  是加载, 则该交叉点的条目是“B” 而不是“X”;如果这些操作是
针对同一地址的, 即使这些操作以程序顺序进入内存顺序, 加载也必须获得刚刚存储的值。 其次,
该表
包括  FENCE,
这在  SC  中不是必需的;  SC  系统的行为就好像在每次操作之前和之后都有一个  
FENCE。

表  4.4:  TSO  排序规则。  “X”
表示强制排序。  “B”
表示如果对同一地址进行操作,
则需要by  
pass。 与  SC  排序规则不同的条目以阴影显示并以粗体显示。

操作2
加载 商店  RMW  围栏
加载 X X X X



店铺 乙 X X X
最小重量X X X X
围栏X X X X

人们普遍认为  x86  内存模型等同于  TSO(用于普通可缓存内存和普通指令), 但据我们所
知,AMD  和英特尔都没有对此做出保证, 也没有发布正式的  x86  内存模型规范。  AMD  和  Intel  在  
Sewell  等人的第  2  节中很好地总结了一个过程中公开定义  x86  内存模型的示例和散文。  [15]。 所
有示例都符合  TSO, 所有散文似乎都符合  TSO。只有公开、 正式的  x86  内存模型描述可用时, 才能证
明这种等价性。 如果反例显示  TSO  不允许的  x86  执行,
x86  不允许的  TSO  执行,
或两者都有,则可
以反驳这种等价性。
Machine Translated by Google

46  4.  总存储顺序和  x86  内存模型

Sewell  等人支持  x86  等同于  TSO。  [15],
在  CACM  中进行了总结, 并在其他地方[9、
14 ]  提供了更多详细信息。 特别是, 作者提出了  x86‑TSO  模型。
该模型有两种形式, 作者证明它们是等价的。 第一种形式提供了一个类似于下一节的图4.4a的
抽象机, 并添加了一个用于建模  x86  LOCK d  指令的全局锁。 第二种形式是标记的过渡系统。
第一种形式使从业者可以访问模型, 而后者简化了形式证明。 一方面,x86‑TSO  似乎与  x86  规
范中的非正式规则和⽯蕊测试一致。

另一方面,
对多个  AMD  和  Intel  平台的实证测试并未显示任何违反  x86‑TSO  模型的行为(但
这并不能证明它们不能)。 总之, 与  Sewell  等人一样,
我们敦促  x86  硬件和软件的创建者采
用明确且可访问的  x86‑TSO  模型。

C1 C2 中⋯
负载 负载
其实现与图  3.4  相同,
除了每个内核  Ci  都有
商店 商店 商店
一个  FIFO  写缓冲区,
用于缓冲存储直到它们进入
内存。
转变

记忆

(a)  使用开关的  TSO  实现

C1 C2 中⋯
负载 负载 是  TSO  实施取代
以类似于为  SC  所做的方式使用高速缓存一
商店 商店 商店

致的内存系统在上面切换。

高速缓存一致
记忆系统

(b)  使用缓存一致性的  TSO  实现

图  4.4:
两个  TSO  实现。
Machine Translated by Google

4.4.实施  TSO/X86  47

4.4  实施  TSO/X86

TSO/x86  的实现过程类似于  SC,
只是增加了每个内核的  FIFO  写缓冲区。
图4.4a更新了图3.4的开关以适应  TSO  并按
如下方式运行。

‧  加载和存储以该内核的程序顺序离开每个内核<p。

‧  负载要么绕过写入缓冲区中的值,
要么像以前一样等待切换。
‧  存储进入FIFO  写缓冲区的尾部,
或者如果缓冲区已满则停止内核。

‧  当开关选择核心Ci时,
它要么执行下一个加载,
要么执行头部的存储
的写缓冲区。

在第3.7  节中,
我们展示了对于  SC,
开关可以由高速缓存一致性内存系统代替,
然后论证了内核可以是推测的
和/或多线程的,
并且非绑定预取可以由内核、
高速缓存或软件启动。

如图4.4b  所示,
同样的论点适用于在每个内核和高速缓存一致性内存系统之间插入  FIFO  写入缓冲区的  TSO。
因此,
除了写入缓冲区之外,
所有先前的  SC  实现讨论都适用于  TSO,
并提供了一种构建  TSO  实现的方法。
此外,
大多
数当前的  TSO  实现似乎只使用上述方法:
采用  SC  实现并插入写缓冲区。

关于写入缓冲区,
推测内核如何准确实现它们的文献和产品空间超出了本章的范围。
例如,
微体系结构可以在物
理上组合存储队列(未提交的存储)
和写入缓冲区(已提交的存储),
和/或在物理上分离加载和存储队列。

最后,
多线程为  TSO  引入了一个微妙的写入缓冲区问题。  TSO  写缓冲区在逻辑上对每个线程上下文(虚拟
核心)
都是私有的。
因此,
在多线程核心上,
一个线程上下文永远不应该绕过另一个线程上下文的写缓冲区。
这种逻辑分
离可以通过每个线程上下文写入缓冲区来实现,
或者更常见的是,
通过使用共享写入缓冲区来实现,
其中条目由线程上
下文标识符标记,
仅当标记匹配时才允许绕过。

闪回测验问题  4:
在具有多线程内核的  TSO  系统中,
线程可能会绕过写入缓冲区中的值,
而不管哪个线程写入了该
值。
对或错?
答:
假的!
线程可能会绕过它写入的值,
但其他线程可能无法看到该值,
直到将存储插入到内存顺序中。

4.4.1  执行原子指令

TSO  中原子  RMW  指令的实现问题类似于  SC  原子指令的实现问题。
关键区别在于  TSO  允许负载通过(即之前订购)
Machine Translated by Google

48  4.  总存储顺序和  x86  内存模型

已写入写入缓冲区的早期存储。
对  RMW  的影响是“写入”
(即存储)
可能会写入写入缓冲区。

为了理解  TSO  中原子  RMW  的实现,
我们将  RMW  视为紧跟在存储之后的加载。

由于  TSO  的排序规则,
RMW  的负载部分无法传递较早的负载。 乍一看,
RMW  的加载部分
可能会传递写入缓冲区中较早的存储, 但这是不合法的。 如果  RMW  的加载部分通过较早的存储,
则  RMW  的存储部分也必须通过较早的存储, 因为  RMW  是原子对。 但是因为TSO不允许store相
互传递, 所以RMW的load部分也不能传递更早的store。

这些对  RMW  的排序约束会影响实现。 因为  RMW  的加载部分无法执行,直到较早的存储已
被排序(即,退出写缓冲区), 原子  RMW  在它可以执行  RMW  的加载部分之前有效地耗尽了写
缓冲区。此外,
为了确保存储部分可以在加载部分之后立即订购, 加载部分需要读写一致性权限,
而不仅仅是正常加载就足够的读权限。 最后, 为了保证  RMW  的原子性,
缓存控制器可能不会放弃
对加载和存储之间的块的一致性许可。

更优化的  RMW  实现是可能的。 例如, 只要  (a)  写入缓冲区中的每个条目在缓存中都具有读


写权限, 并且在缓存中保持读写权限, 直到  RMW  提交, 并且( b)  内核执行负载推测的  MIPS  
R10000  样式检查(第3.8  节)。
从逻辑上讲, 所有早期的存储和加载将作为一个单元(有时称为
“块”) 紧接在  RMW  之前提交。

4.4.2  实施栅栏
支持  TSO  的系统不提供存储和后续(按程序顺序) 加载之间的排序, 尽管它们确实需要加载来
获取较早存储的值。 在程序员希望对这些指令进行排序的情况下, 程序员必须通过在存储和后续
加载之间放置  FENCE  指令来明确指定该排序。  FENCE  的语义指定按程序顺序在  FENCE  之前
的所有指令必须在按程序顺序在  FENCE  之后的任何指令之前排序。

对于支持  TSO  的系统,
FENCE  因此禁止负载绕过较早的存储。 在表4.5  中,
我们重新访问了表4.1
中的示例, 但是我们添加了两个之前没有的  FENCE  指令。 如果没有这些  FENCE,两个负载(L1  
和  L2)
可以绕过两个存储(S1  和  S2),导致  r1  和  r2  都设置为零的执行。 添加的  FENCE  禁止重
新排序, 从而禁止执行。

因为  TSO  只允许一种类型的重新排序,
所以  FENCE  很少见,
FENCE  指令的实现也不是太
关键。
一个简单的实现 比如
Machine Translated by Google

4.4.实施  TSO/X86  49

表  4.5:  r1  和  r2  都可以设置为  0  吗?

核心C1 核心C2 评论

S1:  x  =  新的; S2:
y  =  新; /*  最初,
x  =  0  &  y  =  0  */
栅栏 栅栏

L1:  r1  =  y; L2:  r2  =  x;

在执行  FENCE  时排空写入缓冲区,
并且在更早的  FENCE  提交之前不允许执行后续加载 可
能会提供可接受的性能。
然而, 对于允许更多重新排序的一致性模型(在下一章中讨论), FENCE  指令更加频繁,
它们的实现会对性能产生重大影响。

边栏:
非推测性  TSO  优化此边栏描述了一些高
级非推测性  TSO  优化。
非推测性  TSO  重新排序。
已有论文表明, 加载[11、12 ]和存储[13]都可以非推测性地
重新排序,同时仍然使用相干延迟执行  TSO。 如前所述, 关键的挑战是确保延迟不会导致导
致死锁的循环依赖, 上述所有论文都解决了这个问题。

RMW  没有写入缓冲区漏极。 在第  4.4.1  节中,
我们展示了如何使用推测将写缓冲区
漏极移出关键路径。 拉贾拉姆等人。  [10]表明, 如果重新定义  RMW  的原子性语义, 则可以
非推测性地实现相同的效果。 回想一下,对于  RMW  是原子的, 我们之前要求  RMW  的读取
和写入操作必须连续出现在  TSO  全局内存顺序中。 考虑以下放宽, 只要写入与  RMW  相同
的地址不会出现在全局内存顺序中的读取和写入之间, RMW  就被认为是原子的。 请注意, 这
个宽松的定义与  RMW  的直观定义相匹配, 并且足以让它们在同步情况下使用。 同时, 它允
许  RMW  实现,其中  RMW  的加载部分可以绕过写入缓冲区中的早期存储, 而无需  MIPS  
R10000  样式的加载推测检查。 然而,确保  RMW  原子性需要一致性延迟, 直到写入缓冲区
耗尽, 这会引入死锁危险。 拉贾拉姆等人。  [10]说明如何避免死锁。

通过  FENCE  重新排序。
已经有几篇论文提出了优化的  FENCE  实现[3,  4,  6,  8],

以实现以下内存操作
Machine Translated by Google

50  4.  总存储顺序和  x86  内存模型

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  内存一致性模型的案例研究。
Machine Translated by Google

4.6.比较  SC  和  TSO  51

SC SC

MC1 TSO MC2 MC1 TSO MC2

全部 全部

(a)  处决 (b)  实施(与(a)
相同)

图  4.5:
比较内存一致性模型。

什么是好的内存一致性模型?
一个好的记忆一致性模型应该具备  Sarita  Adve  的  3Ps  [1]加上我们的第四个  P:
‧可编程性:一个好的模型应该(相对)
容易编写多线程程序。该模型对大多数用户来
说应该是直观的,即使是那些没有阅读过详细信息的用户也是如此。它应该是精确
的,这样专家才能突破允许范围。
‧性能:
一个好的模型应该以合理的功率、
成本等促进高性能的实现。
它应该给实现者
提供广泛的选择余地。
‧可移植性:
一个好的模型将被广泛采用或至少提供向后兼容性或模型间转换的能
力。
‧精确:
一个好的模型应该被精确地定义,
通常是用数学来定义的。
自然语言过于模糊,
无法让专家突破允许范围。

SC和TSO有多好?
使用这些  4P:
‧可编程性:  SC  是最直观的。  TSO  很接近,因为对于常见的编程习惯用法,
它的行
为类似于  SC。
然而, 微妙的非  SC  执行会咬程序员和工具作者。

‧性能:对于简单的内核,
TSO  可以提供比  SC  更好的性能,
但通过推测可以使差异变
小。
‧可移植性:  SC  被广泛理解,
而TSO  被广泛采用。
‧精确:  SC  和TSO  被正式定义。
底线是  SC  和  TSO  非常接近,
特别是与更多
复杂和更宽松的内存一致性模型将在下一章讨论。
Machine Translated by Google

52  4.  总存储顺序和  x86  内存模型

4.7  参考文献
[1]  SV  广告。
为共享内存多处理器设计内存一致性模型。 博士威斯康星大学麦迪逊分校计算机科
学系论文, 1993  年  11  月。  51

[2]  WW  科利尔。
关于并行架构的推理。  Prentice‑Hall,  Inc.,  1990.  50

[3]  Y.  Duan、
A.  Muzahid  和  J.  Torrellas。  WeeFence:在  TSO  中免费使用栅栏。 在第  40  届计算
机体系结构国际研讨会上,  2013  年。 DOI:  10.1145/2485922.2485941。  49

[4]  Y.  Duan、
N.  Honarmand  和  J.  Torrellas。
非对称内存栅栏:
优化性能和可实施性。 在过程中。
第  20  届编程语言和操作系统架构支持国际会议,  2015  年。 DOI:  10.1145/2694344.2694388。  
49

[5]  K.  Gharachorloo、D.  Lenoski、J.  Laudon、
P.  Gibbons、
A.  Gupta  和  J.  Hennessy。
可扩展共享内存中的内存一致性和事件排序。 在过程中。 第  17  届年度计算机体系结构国际研讨
会, 第  15‑26  页,
1990  年  5  月。
DOI:  10.1109/isca.1990.134503。  50

[6]  K.  Gharachorloo、M.  Sharma、
S.  Steely  和  S.  Van  Doren。  AlphaServer  GS320  的架构和
设计。 在过程中。 第  9  届编程语言和操作系统架构支持国际会议,  2000  年。 DOI:
10.1145/378993.378997。  49

[7]  小古德曼。 缓存一致性和顺序一致性。
技术报告  1006,
威斯康星大学麦迪逊分校计算机科学系,
1991  年  2  月。  50

[8]  C.  Lin、
V.  Nagarajan  和  R.  Gupta。使用条件栅栏的高效顺序一致性。 第19  届并行架构和编译
技术国际会议,  2010  年。 DOI: 10.1145/1854273.1854312。  49

[9]  S.  Owens、
S.  Sarkar  和  P.  Sewell。
更好的  x86  内存模型: x86‑TSO。 在过程中。
高阶逻辑定理
证明会议,  2009.  DOI: 10.1007/978‑3‑642‑03359‑9_27 。  46,  50

[10]  B.  Rajaram、
V.  Nagarajan、
S.  Sarkar  和  M.  Elver。  TSO  的快速  RMW: 语义和实现。 在过程
中。  ACM  SIGPLAN  编程语言设计与实现会议,  2013  年。 DOI:
10.1145/2491956.2462196。  
49

[11]  A.  Ros、
TE  Carlson、
M.  Alipour  和  S.  Kaxiras。  TSO  中的非推测负载‑负载重新排序。
在过程
中。  2017  年第  44  届计算机体系结构国际研讨会。
DOI:
10.1145/3079856.3080220。  49
Machine Translated by Google

4.7.参考文献  53

[12]  A.  Ros  和  S.  Kaxiras。
多余的加载队列。 在第  51  届  IEEE/ACM  微架构国际研讨会上,  2018  年。
DOI: 10.1109/micro.2018.00017。  49

[13]  A.  Ros  和  S.  Kaxiras。
非投机商店在总商店订单中合并。 在第  45  届  ACM/IEEE  年度计算机体系结
构国际研讨会上,  2018  年。 DOI:  10.1109/isca.2018.00028。  49

[14]  S.  Sarkar、
P.  Sewell、FZ  Nardelli、
S.  Owens、
T.  Ridge、
T.  Braibant、
MO  Myreen  和  J.
Alglave.x86‑CC  多处理器机器代码的语义。 在过程中。 第  36  届年度  ACM  SIGPLAN‑SIGACT  编程
语言原理研讨会, 第  379–391  页, 2009  年。DOI:
10.1145/1480881.1480929。  46,  50

[15]  P.  Sewell、
S.  Sarkar、
S.  Owens、
FZ  Nardelli  和  MO  Myreen。  x86‑TSO:用于  x86  多处理器的
严格且可用的程序员模型。  ACM  通讯,  2010  年  7  月。 DOI:10.1145/1785414.1785443。  45,  
46,  50

[16]  P.  Sindhu,  J.‑M.  Frailong  和  M.  Ceklov。
内存模型的正式规范。
技术报告  CSL‑91–11, 施乐帕洛阿尔托研究中心, 1991  年  12  月。
DOI:  
10.1007/978‑1‑4615‑3604‑8_2。  50
Machine Translated by Google
Machine Translated by Google

55

第  5  章

宽松的内存一致性
前两章探讨了内存一致性模型顺序一致性(SC) 和总存储顺序(TSO)。 这些章节将  SC  
描述为直观的,将  TSO  描述为广泛实现的(例如, 在  x86  中)。
这两种模型有时都称为
强,
因为每个模型的全局内存顺序通常尊重(保留) 每线程程序顺序。 回想一下, 对于加载
和存储的所有四种组合(加载! 加载、 加载! 存储、存储! 存储和存储! 加载),  SC  为来自同
一线程的两个内存操作保留顺序, 而  TSO  保留前三个顺序但不保留商店! 加载订单。

本章将研究更宽松(弱)的内存一致性模型,
这些模型旨在仅保留程序员“需要”的
顺序。这种方法的主要好处是,
更少的排序约束可以通过允许更多的硬件和软件(编译器
和运行时系统)优化来促进更高的性能。主要缺点是松弛模型必须在“需要”
排序时形式
化,
并为程序员或低级软件提供将此类排序传达给实现的机制, 并且供应商未能就单一松
弛模型达成一致,从而损害了可移植性。

对宽松一致性模型的全面探索超出了本章的范围。
相反, 本章是一本入门书, 旨在提供基本的直觉, 并帮助读者意识到简单理解这些模型的
局限性。 特别是, 我们提供松弛模型的动机(第5.1  节), 呈现并形式化一个示例松弛一
致性模型  XC(第5.2  节), 讨论  XC  的实现, 包括原子指令和用于强制排序的指令(第
5.3  节),介绍无数据争用程序的顺序一致性(第5.4  节), 介绍其他宽松模型概念(第
5.5  节),介绍  RISC‑V  和  IBM  Power  内存模型案例研究(第5.6  节),
指向进一步阅读
和其他商业模型(第  5.7节), 比较模型(第5.8  节), 以及高级语言记忆模型(第5.9  
节)。

5.1  动机
正如我们很快就会看到的那样,
掌握宽松的一致性模型比理解  SC  和  TSO  更具挑战性。
这些缺点引出了一个问题:
为什么要费心使用宽松的模型呢? 在本节中, 我们将激发松弛
模型,
首先展示程序员不关心指令顺序的一些常见情况(第5.1.1  节) , 然后讨论一些在
不强制执行不必要的顺序时可以利用的优化(第5.1.2  节)。
Machine Translated by Google

56  5.  放松记忆一致性

5.1.1  重新排序内存操作的机会

考虑表  5.1  中描述的示例。 大多数程序员会期望  r2  将始终获得值  NEW, 因为  S1  在  S3  之前,


而  S3  
在加载值  SET  的  L1  的动态实例之前,
而  S3  在  L2  之前。
我们可以这样表示:

‧  S1 !  S3 !  L1  加载  SET !  L2。

同样,
大多数程序员会期望  r3  总是获得  NEW  值,
因为:
‧  S2 !  S3 !  L1  加载  SET !  L3。

除了上面这两个预期订单外, SC  和  TSO  还需要订单  S1 !
S2  和  L2 !  L3。
保留这些额外的命令可能会限制实施优化以帮助提高性能, 但程序不需要这些额外
的命令来进行正确操作。
表5.2描述了使用同一个锁在两个临界区之间切换的更一般情况。 假设硬件支持锁获取(例如,
通过测试和设置执行读修改写和循环直到成功) 和锁释放(例如, 存储值  0)。让核心  C1  获取锁,执
行临界区  1, 任意交错加载  (L1i)  和存储  (S1j),然后释放锁。 类似地, 让核心  C2  执行关键部分  2,包括
加载  (L2i)  和存储  (S2j)  的任意交错。

从临界区  1  到临界区  2  的切换的正确操作取决于这些操作的顺序:

‧  所有L1i,
所有S1j !  R1 !  A2 !
所有  L2i,
所有  S2j,

其中逗号(“,”)
分隔未指定顺序的操作。
正确的操作不依赖于每个临界区内加载和存储的任何排序 除非操作是针对相同的地址(在
这种情况下,需要排序来维护顺序处理器排序)。
那是:

‧  所有  L1i  和  S1j  都可以按任何顺序排列,
并且

表  5.1:
什么顺序确保  r2  和  r3  总是  NEW?

核心C1 核心C2 评论

S1:
数据  1  =  新的; /*  最初,
data1  和  data2  =  0  &  fl  ag  ≠  SET  */
S2:
数据  2  =  新的;
S3:
标志=设置; L1:  r1  =  fl  ag /*  自旋循环:
L1  &  B1  可能重复多次  */
B1:
如果(r1≠SET)
转到L1;
L2:
r2=数据1;
L3:
r3=数据2;
Machine Translated by Google

5.1.动机  57
表  5.2:
什么顺序确保从临界区  1  到临界区  2  的正确切换?

核心C1 核心C2 评论
A1:
获取(锁定)
/*  开始关键部分  1  */
一些负载  L1i  交错 /*  L1i  和  S1j  的任意交错  */

与一些商店  S1j
/*  结束关键部分  1  */ /*  从临界区  1  切换  */

R1:
释放(锁定) A2:
获取(锁定) /*  到临界区  2  */

/*  开始关键部分  2  */
一些负载  L2i  交错 /*  L2i  和  S2j  的任意交错  */

与一些商店  S2j
/*  结束关键部分  2  */

R2:
释放(锁定)

‧  所有L2i  和S2j  可以相互之间以任何顺序排列。

如果正确的操作不依赖于许多加载和存储之间的排序,也许可以通过放宽它们之间的顺序来获得更高的
性能,
因为加载和存储通常比锁获取和释放更频繁。 这就是放松模型或弱模型所做的。

5.1.2  利用重新排序的机会
现在假设一个宽松的内存一致性模型,
允许我们重新排序任何内存操作,除非它们之间有  FENCE。
这种宽松的模
型迫使程序员推理哪些操作需要排序,
这是一个缺点,但它也支持许多可以提高性能的优化。 我们讨论了一些常见
且重要的优化,
但对这个主题的深入处理超出了本入门指南的范围。

非  FIFO,
合并写缓冲区回想一下,
TSO  支持
使用  FIFO  写缓冲区,
它通过隐藏提交存储的部分或全部延迟来提高性能。 尽管  FIFO  写入缓冲区提高了性能,
但更优化的设计将使用允许合并写入的非  FIFO  写入缓冲区(即, 程序顺序不连续的两个存储可以写入写入缓
冲区中的相同条目)。 但是,非  FIFO  合并写入缓冲区通常会违反  TSO,
因为  TSO  要求存储按程序顺序出现。 我们
的示例宽松模型允许存储在非  FIFO  写入缓冲区中合并, 只要存储未被  FENCE  分隔即可。
Machine Translated by Google

58  5.  放松记忆一致性

对内核推测的更简单支持在具有强一致
性模型的系统中, 内核可能会在准备好提交之前推测性地执行程序顺序外的负载。 回想一下支持  
SC  的  MIPS  R10000  内核如何使用这种推测来获得比没有推测的简单实现更好的性能。 然而,要注
意的是, 支持  SC  的推测核心通常必须包括检查推测是否正确的机制, 即使错误推测很少见[15,  
21]。  R10000  通过将逐出的高速缓存块的地址与内核已推测加载但尚未提交的地址列表(即内
核加载队列的内容) 进行比较来检查推测。 这种机制增加了硬件的成本和复杂性,
它消耗了额外的功
率, 并且它代表了可能限制指令级并行性的另一种有限资源。 在具有宽松内存一致性模型的系统中,
内核可以不按程序顺序执行加载, 而不必将这些加载的地址与传入一致性请求的地址进行比较。 这
些负载对于宽松的一致性模型而言不是推测性的(尽管它们可能是关于分支预测或同一线程较早
存储到同一地址的推测性的)。

耦合一致性和连贯性我们之前提倡解
耦一致性和连贯性来管理智力复杂性。 或者,通过“打开一致性盒”, 松弛模型可以提供比强模型
更好的性能。
例如, 一个实现可能允许核心的子集从存储中加载新值, 即使其余的核心仍然可以加载
旧值,
暂时打破一致性的单写入器  ‑  多读取器不变性。 这种情况可能会发生, 例如,当两个线程上下
文在逻辑上共享每个内核的写缓冲区或当两个内核共享一个  L1  数据缓存时。 然而,“打开相干盒”
会带来相当大的智力和验证复杂性, 让人想起希腊神话中的潘多拉魔盒。 正如我们将在第  5.6.2  节中
讨论的那样,  IBM  Power  允许上述优化。我们还将在第10章探讨GPU  和异构处理器为什么以及
如何在强制一致性的同时打开一致性框。 但首先我们探索紧闭相干盒的放松模型。

5.2  松弛一致性模型  (XC)  示例

出于教学目的, 本节介绍了一个松弛一致性模型  (XC)  示例, 它捕获了松弛内存一致性模型的基本思


想和一些实现潜力。  XC  假设存在全局内存顺序, 对于  SC  和  TSO  的强大模型以及  Alpha  [33]和  
SPARC  松弛内存顺序  (RMO)  [34]  的大部分已失效的松弛模型也是如此。
Machine Translated by Google

5.2.放松一致性模型  (XC)  示例  59
5.2.1  XC  模型的基本思想

XC  提供了一个  FENCE  指令,
以便程序员可以指示何时需要命令;
否则,
默认情况下,
加载和存储是无序的。
其他宽松
的一致性模型将  FENCE  称为屏障、
内存屏障、
内存栏或同步。
让核心  Ci  执行一些加载和/或存储,
Xi,
然后执行  FENCE  
指令,
然后执行更多加载和/或存储, Yi。  FENCE  确保内存顺序将在  FENCE  之前对所有  Xi  操作进行排序, 而  FENCE  
又在所有  Yi  操作之前。  FENCE  指令不指定地址。 同一个核心的两个  FENCE  也保持有序。 但是, FENCE  不会影响其
他内核的内存操作顺序(这就是为什么“fence”
可能比“barrier”
更好的名称)。

一些架构包含多个具有不同排序属性的  FENCE  指令;
例如,
一个体系结构可以包括一个  FENCE  指令,
该指令强制执
行除从存储到后续加载之外的所有顺序。
然而,
在本章中,
我们只考虑对所有类型的操作进行排序的  FENCE。

XC  的内存顺序保证尊重(保留)
程序顺序:

‧  加载!
栅栏

‧  商店!
栅栏

‧  栅栏!
栅栏

‧  栅栏!
加载

‧  栅栏!
店铺

XC  维护  TSO  规则,
仅对同一地址进行两次访问:

‧  加载!
加载到相同地址

‧  加载!
存放到同一个地址

‧  商店!
存放到同一个地址

这些规则强制执行顺序处理器模型(即顺序核心语义)
并禁止可能使程序员感到惊讶的行为。
例如,
商店!
存储
规则防止执行“A  =  1”
然后“A  =  2”
的关键部分奇怪地完成  A  设置为  1。
同样,
Load !加载规则确保如果  B  最初为  0  
并且另一个线程执行“B  =  1”,
则当前线程不能执行“r1  =  B”
然后“r2  =  B”,
r1  获得  1  且  r2  获得  0,
就好像  B  的
值去从新到旧。

XC  确保负载立即看到由于它们自己的存储而产生的更新(就像  TSO  的写缓冲区绕过)。
这条规则保留了单
线程的顺序性,
也是为了避免让程序员感到惊讶。
Machine Translated by Google

60  5.  放松记忆的一致性

5.2.2  在  XC  下使用  FENCES  的例子

表5.3显示了程序员或低级软件应如何在表5.1的程序中插入  FENCE ,
以便它在  XC  下正确运行。
这些  
FENCE  确保:

‧  S1,
S2 !  F1 !  S3 !  L1  加载  SET !  F2 !  L2,
L3。

订购商店的  F1  FENCE  对大多数读者来说都很有意义, 但有些人对  F2  FENCE  订购货物的需
求感到惊讶。但是,
如果允许加载无序执行, 则它们可以使其看起来像是无序执行的有序存储。 例如,

果执行可以按照L2、 S1、 S2、
S3、 L1、
L3进行,
那么L2可以获得0值。
对于不包含B1控制依赖的程序尤其
可能出现这种结果, 使得L1和L2  是连续加载到不同的地址, 其中重新排序似乎是合理的, 但事实并非
如此。

表  5.3:
将  XC  的  FENCE  添加到表5.1  的程序中

核心C1 核心C2 评论
S1:
数据  1  =  新的; /*  最初,
data1  &  data2  =  0  &  fl  ag  ≠  SET  */
S2:
数据  2  =  新的;
F1:
栅栏

S3:
标志=设置; L1:  r1  =  fl  ag; /*  L1  &  B1  可以重复多次  */

B1:
如果(r1≠SET)
转到L1;
F2:
栅栏

L2:
r2=数据1;
L3:
r3=数据2;

表5.4显示了程序员或低级软件如何在表5.2的临界区程序中插入  FENCE , 以便它在  XC  下正
确运行。这个  FENCE  in  sertion  策略,
其中  FENCE  围绕每个锁获取和锁释放, 出于说明目的是保守
的;
稍后我们将展示其中一些  FENCE  是可以移除的。 特别是,FENCE  F13  和  F22  确保关键部分之间
的正确切换, 因为:

‧  所有L1i,
所有S1j !  F13 !  R11 !  A21 !  F22 !
所有  L2i,
所有  S2j
接下来,
我们将  XC  形式化,
然后说明上述两个示例为何有效。

5.2.3  形式化  XC

在这里, 我们以与前两章的符号和方法一致的方式将  XC  形式化。 再一次,令  L(a)  和  S(a)  分别表示对


地址  a  的加载和存储。
Orders  <p  和  <m  分别定义了每个处理器的程序顺序和全局内存顺序。
Machine Translated by Google

5.2.松弛一致性模型  (XC)  示例  61表  5.4:
为  XC  添加  FENCE  到表5.2  的临界区程序。  

(请注意,
并非所有  FENCES  都是正确性所必需的。)

核心C1 核心C2 评论
F11:
栅栏

A11:
获取(锁定)
F12:
栅栏

一些负载  L1i  交错 /*  L1i  和  S1j  的任意交错  */

与一些商店  S1j
F13:
栅栏

R11:
释放(锁定) F21:
栅栏 /*  从临界区  1  切换  */

F14:
栅栏 A21:
获取(锁定) /*  到临界区  2  */

F22:
栅栏

一些负载  L2i  交错 /*  L2i  和  S2j  的任意交错  */
与一些商店  S2j
F23:
栅栏

R22:
释放(锁定)
F24:
栅栏

Program  order  <p  是每个处理器的总顺序,
它捕获每个内核逻辑上(顺序)
执行内存操作的顺序。  global  memory  order  
<m  是所有核的内存操作的总顺序。

更正式地说,  XC  执行需要以下内容。

1.  所有核心将它们的加载、
存储和  FENCE  插入到  <m  的顺序中:

‧  如果L(a)  <p  FENCE )  L(a)  <m  FENCE  ‧  如果S(a)   /*  加载!
栅栏  */

<p  FENCE )  S(a)  <m  FENCE  ‧  如果FENCE  <p  FENCE )   /*  存储!
栅栏  */

FENCE  <m  FENCE /*  FENCE !  FENCE  */  ‧  如果  FENCE  <p  L(a) )  FENCE  <m  L(a)  ‧  如果  FENCE  

<p  S(a) )  FENCE  <m  S(a) /*  栅栏!
加载  */

/*  栅栏!
店铺  */

2.  所有内核将它们的加载和存储插入到相同地址的顺序  <m  表示:

‧  如果  L(a)  <p  L (a) )  L(a)  <m  L  (a)   /*  加载!


加载到同一个地址  */

‧  如果  L(a)  <p  S(a) )  L(a)  <m  S(a)  ‧   /*  加载!
存储到相同的地址  */

如果S(a)  <p  S (a) )  S(a)  <m  S  (a) /*  存储!


存储到相同的地址  */
Machine Translated by Google

62  5.  放松记忆的一致性

3.  每次加载都从它之前的最后一个存储中获取它的值到相同的地址:
L(a)  的值  =  MAX  的值<m  {S(a)  |  S(a)  <m  L(a)  或  S(a)  <p  L(a)} /*  像  TSO  */

我们在表  5.5  中总结了这些排序规则。 该表与  SC  和  TSO  的类似表有很大不同。从视觉上看,
该表显示仅对相同地址的操作或使用  FENCE  时才强制执行排序。 和TSO一样,
如果操作1是“store  
C”,
操作2是“load  C”, store可以在load之后进入全局顺序, 但是load必须已经看到了新存储的
值。

只允许  XC  执行的实现是XC  实现。

表  5.5:
XC排序规则。  “X”
表示强制排序。  “B”
表示如果操作是针对同一地址, 则需要旁路。  “A”
表示仅当操作针对同一地址时才强制执行的排序。 与  TSO  不同的条目加阴影并以粗体显示。

操作2
加载 商店  RMW  围栏
加载 A A A X



店铺 乙 A A X
RMW  A A A X
围栏X X X X

5.2.4  显示  XC  正确运行的示例

有了上一节的形式主义, 我们现在可以揭示为什么第5.2.2  节的两个例子能正确工作。 图5.1a显示了


表5.3中示例的  XC  执行,
其中核心  C1  的存储  S1  和  S2  被重新排序,
核心  C2  的加载  L2  和  L3  也是
如此。 但是, 重新排序都不会影响程序的结果。 因此, 据程序员所知, 此  XC  执行等同于图5.1b  中描述
的  SC  执行,
其中两对操作未重新排序。

类似地,图5.2a描述了表5.4中的临界区示例的执行, 其中核心  C1  的加载  L1i  和存储  S1j  相互
重新排序,核心  C2  的  L2i  和存储  S2j  也是如此。
再一次,
这些重新排序不会影响程序的结果。

因此,
据程序员所知,
此  XC  执行等同于图5.2b  中描述的  SC  执行,
其中没有重新排序加载或存储。

这些示例表明,
如果有足够的  FENCE,
像  XC  这样的宽松模型可以在程序员看来是  SC。  5.4
节讨论了从这些示例中进行概括,
但首先让我们实现  XC。
Machine Translated by Google

5.2.放松一致性模型  (XC)  示例  63

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

S1:
数据  1  =  新的; /*  新的  */
L1:
r1  =  标志; /*  0  */

S2:
数据  2  =  新的; /*  新的  */
L1:
r1  =  标志; /*  0  */
F1:
栅栏
L1:
r1  =  标志; /*  0  */
S3:
标志=设置; /*  放  */

L1:
r1  =  标志; /*  放  */
F1:
栅栏

L2:
r2=数据1; /*  新的  */

L3:
r3=数据2; /*  新的  */

(a)  XC  执行

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)

S1:
数据  1  =  新的; /*  新的  */
L1:
r1  =  标志; /*  0  */
S2:
数据  2  =  新的; /*  新的  */
L1:
r1  =  标志; /*  0  */
F1:
栅栏
L1:
r1  =  标志; /*  0  */
S3:
标志=设置; /*  放  */
L1:
r1  =  标志; /*  放  */

F1:
栅栏

L2:
r2=数据1; /*  新的  */

L3:
r3=数据2; /*  新的  */

(b)  SC  执行

图  5.1:
表5.3程序的两个等效执行。
Machine Translated by Google

64  5.  放松记忆的一致性

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)


F11:
栅栏
A11:
获取(锁定)
F12:
栅栏

一些负载  L1i  &  存储  S1j

F13:
栅栏
F21:
栅栏
R11:
释放(锁定)
F14:
栅栏 A21:
获取(锁定)
F22:
栅栏

一些负载  L12i  &  存储  S2j
F23:
栅栏
R22:
释放(锁定)
F24:
栅栏
(a)  XC  执行

核心  C1  的程序顺序  (<p) 内存顺序  (<m) 核心  C2  的程序顺序  (<p)


F11:
栅栏
A11:
获取(锁定)
F12:
栅栏

一些负载  L1i  &  存储  S1j

F13:
栅栏
F21:
栅栏
R11:
释放(锁定)
F14:
栅栏 A21:
获取(锁定)
F22:
栅栏

一些负载  L12i  &  存储  S2j
F23:
栅栏
R22:
释放(锁定)
F24:
栅栏

(b)  SC  执行

图  5.2:
表5.4临界区程序的两次等效执行。
Machine Translated by Google

5.3.实施  XC  65

5.3  实施  XC
本节讨论实现  XC。
我们遵循类似于前两章中用于实现  SC  和  TSO  的方法,其中我们将核心操作的重
新排序与缓存一致性分开。 回想一下,
TSO  系统中的每个内核都通过  FIFO  写入缓冲区与共享内存分
开。
对于  XC,
每个内核将通过一个更通用的重新排序单元与内存分离, 该重新排序单元可以重新排序
加载和存储。

如图5.3a所示,  XC  的运行方式如下。

‧  加载、
存储和  FENCE  以  Ci  的程序顺序  <p  离开每个核心  Ci  并进入尾部
Ci  的重新排序单元。

‧  Ci  的重新排序单元将操作排队并将它们从它的尾部传递到它的头部,按照程序顺序或按照下
面指定的规则重新排序。  FENCE  在到达重新排序单元的头部时被丢弃。

C1 C2 中⋯

XC  实现是仿照上一章的  SC  和  TSO  开关实现建
重新排序 重新排序 重新排序
模的,
除了更通用的重新排序单元将内核和内存开关
转变 分开。

记忆

(a)  使用开关的  XC  实现

C1 C2 中⋯
是  XC  实现取代了
重新排序 重新排序 重新排序 以类似于前几章中为  SC  和  TSO  所做的方
式,
使用高速缓存一致的内存系统在上面进行切
换。
高速缓存一致
记忆系统

(b)  使用缓存一致性的  XC  实现

图  5.3:
两个  XC  实现。
Machine Translated by Google

66  5.放松记忆一致性

‧  当交换机选择核心Ci时,
它在Ci的重新排序的头部执行加载或存储
单元。

重新排序单元遵守以下规则:
(1)  FENCE,
(2)  对同一地址的操作,
以及  (3)  绕过。

1.  FENCE  可以通过几种不同的方式实现(参见第5.3.2  节),
但它们必须强制执行命令。具体
而言, 无论地址如何, 重新排序单元都可能不会重新排序: 加载!栅栏,
商店!
篱笆,篱笆!
篱笆,
篱笆!
加载,
或  FENCE !
店铺

2、
对于同一地址,
补货单位不得补货:
加载!
加载,
加载!
商店,
商店!
存储(到同一地址)

3.  再订货单位必须确保货物立即看到由于他们自己的商店而更新。

毫不奇怪, 所有这些规则都反映了第5.2.3  节的规则。
在前两章中, 我们讨论了  SC  和  TSO  实现中的开关和内存可以被高速缓存一致的内存系统
取代。如图  5.3b  所示,
同样的论点也适用于  XC 。 因此,对于  SC  和  TSO,
XC  实现可以将核心(重
新)
排序规则与缓存一致性的实现分开。 和以前一样, 缓存一致性实现了全局内存顺序。 新的是,
由于
重新排序单元重新排序, 内存顺序可能更经常地不遵守程序顺序。

那么从  TSO  转移到像  XC  这样的宽松模型能提供多少性能呢?
不幸的是,正确的答案取决于第  5.1.2  节中讨论的因素, 例如  FIFO  与合并写缓冲区和推测支持。

在  1990  年代后期,我们中的一个人将推测核心的趋势视为削弱了宽松模型(更好的性能)
存在的理由, 并主张返回到更简单的  SC  或  TSO  接口[22]。
虽然我们仍然相信简单的界面是好的,
但这种转变并没有发生。 原因之一是企业的动力。 另一个原因是, 由于嵌入式芯片和/或具有许多(非
对称)
内核的芯片中的功率限制部署, 并非所有未来的内核都具有高度的推测性。

5.3.1  XC  的原子指令
在支持  XC  的系统中实现原子  RMW  指令有几种可行的方法。  RMW  的实现也取决于系统如何实
现  XC;
在本节中, 我们假设  XC  系统由动态调度的内核组成,
每个内核都通过非  FIFO  合并写入缓
冲区连接到内存系统。

在这个  XC  系统模型中,
一个简单可行的解决方案是借用我们用于  TSO  的实现。
在执行原
子指令之前,内核排空写缓冲区, 获得具有读写一致性权限的块,
然后执行加载部分
Machine Translated by Google

5.3.实施  XC  67

和商店部分。因为block处于读写状态,store部分直接对cache执行,
绕过了write  buffer。
在加载部
分执行和存储部分执行之间, 如果存在这样的窗口, 则缓存控制器不得驱逐该块; 如果传入的一致性
请求到达,
则必须将其推迟到  RMW  的存储部分执行。

借用  TSO  方案实现  RMW  很简单, 但过于保守,
牺牲了一些性能。值得注意的是,
不需要耗
尽写入缓冲区, 因为  XC  允许  RMW  的加载部分和存储部分通过较早的存储。因此,
只需获得对块的
读写一致性权限, 然后执行加载部分和存储部分, 而无需在这两个操作之间放弃块就足够了。

原子  RMW  的其他实现是可能的, 但它们超出了本入门教程的范围。  XC  和  TSO  之间的一
个重要区别是如何使用原子  RMW  来实现同步。 在表5.6  中,
我们描述了一个典型的临界区, 包括  
TSO  和  XC  的锁获取和锁释放。 使用  TSO,
原子  RMW  用于尝试获取锁, 存储用于释放锁。 使用  XC,
情况就更复杂了。 对于  acquire,
默认情况下, XC  不会限制  RMW  对临界区中的操作进行重新排序。
为避免这种情况, 锁获取后必须跟有  FENCE。 类似地, 默认情况下, 锁释放不受限制,不能根据临界
区中它之前的操作进行重新排序。 为避免这种情况, 锁释放之前必须有一个  FENCE。

表  5.6:  TSO  中的同步与  XC  中的同步

代号  TSO XC

获得 RMW:  test‑and‑set  L /  *  读L,
写L=1  */ RMW:  test‑and‑set  L /  *  read  L,  write  L=1  */  if  
锁 L==1,  goto  RMW /*  if  Lock  held,  try  again  */  if  L==1,  
*/goto  RMW /*  if  lock  held  复制代码,  再试一次  
栅栏

批判的 加载和存储 加载和存储


部分
发布 商店L=0 栅栏
锁 商店L=0

5.3.2  带  XC  的栅栏
如果核心  C1  执行一些内存操作  Xi, 然后执行  FENCE, 然后执行内存操作  Yi,
则  XC  实现必须保持
顺序。
具体来说, XC  实现必须命令  Xi  <m  FENCE  <m  Yi。
我们看到三种基本方法:
Machine Translated by Google

68  5.放松记忆一致性

‧  一个实现可以实现SC  并将所有FENCE  视为空操作。
这还没有在商业产品中完成,
但已经
有学术建议, 例如,
通过隐式事务记忆[16]。

‧  一个实现可以等待所有内存操作Xi  执行,认为FENCE  完成, 然后开始内存操作Yi。
这种
“FENCE  作为排水管”
的方法很常见, 但它使  FENCE  成本很高。

‧  一个实施可以积极地向必要的方向推进,
强制执行  Xi  <m  FENCE  <m  Yi,
而不会耗尽。

体如何做到这一点超出了本入门教程的范围。 虽然这种方法的设计和验证可能更复杂, 但
它可以带来比耗尽更好的性能。

在所有情况下,
FENCE  实现必须知道每个操作  Xi  何时完成(或至少有序)。
对于绕过通
常缓存一致性的存储(例如, 存储到  I/O  设备或使用一些奇特的写入更新优化的存储)来说,

解操作何时完成可能特别棘手。

5.3.3  警告

最后,
XC  实施者可能会说, “我正在实施一个宽松的模型, 所以一切顺利。”
这不是真的。 必须遵守许多  XC  规则,例如, 加载! 将顺序加载到同一地址(这个特定的顺序实际
上在乱序内核中执行起来并不简单)。 此外, 所有  XC  实现都必须足够强大,
以便为每对指令之
间具有  FENCE  的程序提供  SC,
因为这些  FENCE  需要内存顺序以遵守所有程序顺序。

5.4  无数据竞争程序的顺序一致性

儿童和计算机架构师希望“既能吃到蛋糕, 也能吃到蛋糕”。
对于内存一致性模型, 这可能意味
着使程序员能够使用(相对)
直观的  SC  模型进行推理,
同时仍然实现在像  XC  这样的宽松模型
上执行的性能优势。

幸运的是, 对于一类重要的无数据竞争  (DRF)程序[3],
同时实现这两个目标是可能的。 通
俗地讲, 当两个线程访问同一内存位置时会发生数据竞争, 至少其中一个访问是写操作,并且没
有干预同步操作。 数据竞争通常(但不总是) 是编程错误的结果, 许多程序员都试图编写  DRF  程
序。  DRF  编程的  SC要求程序员通过编写正确同步的程序和标记同步操作来确保程序是  SC  下
的  DRF;
然后它要求实施者确保所有执行
Machine Translated by Google

5.4.无数据竞争程序的顺序一致性  69
通过将标记的同步操作映射到松弛内存模型支持的  FENCE  和  RMW, 松弛模型上的大部分  
DRF  程序也是  SC  执行。  XC  和大多数商业宽松内存模型具有恢复  SC  所需的  FENCE  指令和  
RMW。 此外,
这种方法还作为  Java  和CCC高级语言  (HLL)  内存模型(第5.9  节)
的基础。

表  5.7:
具有数据竞争的  XC  的四个结果示例

核心C1 核心C2 评论
F11:
栅栏 /*  最初,
data1  &  data2  =  0  */
A11:
获取(锁定)
F12:
栅栏

S1:
数据  1  =  新的; L1:
r2=数据2;

S2:
数据  2  =  新的; L2:
r1=数据1; /*  XC  下的四种可能结果:
F13:
栅栏 (r1,  r2)  =
R11:
释放(锁定) (0,  0),  (0,  新),  (新,  0),  或  (新,  新)
F14:
栅栏 但是有数据竞争  */

表  5.8:
没有数据竞争的  XC  的两个结果示例,
就像  SC

核心C1 核心C2 评论
F11:
栅栏 /*  最初,
data1  &  data2  =  0  */
A11:
获取(锁定)
F12:
栅栏

S1:
数据  1  =  新的;
S2:
数据  2  =  新的;
F13:
栅栏

R11:
释放(锁定) F21:
栅栏
F14:
栅栏 A21:
获取(锁定)
F22:
栅栏

L1:
r2=数据2;
L2:
r1=数据1; /*  XC  下的两种可能结果:
F23:
栅栏 (r1,  r2)  =  
R22:
释放(锁定) (0,  0)  或  (新,  新)
F24:
栅栏 与  SC  相同  */
Machine Translated by Google

70  5.  放松记忆的一致性

让我们用两个例子来激励“SC  for  DRF”。
表5.7和表5.8均描述了示例,其中核心  C1  存储两
个位置(S1  和  S2),核心  C2  以相反的顺序加载两个位置(L1  和  L2)。
这些示例有所不同, 因为核
心  C2  在表5.7中没有同步,
但在表  5.8  中获取了与核心  C1  相同的锁。

由于核心  C2  在表  5.7  中没有同步, 它的加载可以与核心  C1  的存储同时执行。 由于  XC  允许  


Core  C1  重新排序存储  S1  和  S2(或不) 以及  Core  C2  重新排序负载  L1  和  L2(或不重新排序),
因此可能有四种结果, 其中  (r1,  r2)  =  (0,  0),  (0,  NEW ),  (NEW,  0),  或  (NEW,  NEW)。
例如, 如果加载
和存储按顺序  S2、 L1、 L2  然后  S1  或顺序  L2、 S1、 S2  然后  L1  执行, 则会出现输出  (0,  NEW)。 但是, 此
示例包含两个数据竞争(S1  与  L2  和  S2  与  L1), 因为核心  C2  未获取核心  C1  使用的锁。

表5.8描述了  Core  C2  获得与  Core  C1  相同的锁的情况。 在这种情况下, 核心  C1  的临界区将


在核心  C2  之前完全执行, 反之亦然。
这允许两种结果: (r1,  r2)  =  (0,  0)  或  (NEW,  NEW)。
重要的是, 这些结果不受核心  C1  是否重新排序
存储  S1  和  S2  和/或核心  C2  是否重新排序加载  L1  和  L2  的影响。  “一棵树倒在树林里(重新排
序的商店), 但没人听到(没有并发负载)。” 此外,XC  结果与  SC  允许的结果相同。  “SC  for  
DRF”从这两个例子中归纳出:

‧  执行中的数据竞争会暴露  XC  对加载或存储的重新排序,
或者

‧  XC  执行无数据争用且与SC  执行没有区别。

更具体地理解“SC  for  DRF”
需要一些定义。

‧  一些内存操作被标记为同步(“同步操作”),
而其余的默认标记为数据(“数据操作”)。
同步操作包括锁获取和释放。

‧  如果两个数据操作Di  和Dj来自不同的核心(线程)
(即,
不按程序顺序排序),
访问相同的内
存位置, 并且至少有一个是存储, 则它们会发生冲突。

‧  如果两个同步操作  Si  和  Sj来自不同的核心(线程),
访问相同的内存位置(例如,
相同的
锁), 并且至少一个同步操作是写入(例如, 获取和释放自旋锁冲突,
而读写锁上的两个  
read_lock  则不会)。

‧  如果  Si  和  Sj  冲突或者  Si  与某个同步操作Sk  冲突,
则两个同步操作  Si  和  Sj  传递冲突,
Sk  <  
p  Sk (即, Sk  在核心  K  的程序顺序中早于  Sk ),并且  Sk 与  Sj  传递冲突。

‧  如果两个数据操作  Di  和  Dj发生冲突并且它们出现在全局内存顺序中而没有一对可传递的冲
突同步操作介入, 则它们会竞争
Machine Translated by Google

5.4.无数据竞争程序的顺序一致性  71
由相同的核心(线程) i  和  j。
换句话说,
当且仅当存在一对传递冲突的同步操作  Si  和  Sj  使得  Di  
<m  Si  <m  Sj  <m  Dj  时, 一对冲突数据操作  Di  <m  Dj  不是数据竞争。

‧  如果没有数据操作竞争,
则SC  执行是无数据竞争(DRF)  的。
‧  如果一个程序的所有SC  执行都是DRF,
则该程序是DRF。

‧  如果所有DRF  程序的所有执行都是SC  执行,
则内存一致性模型支持“SC  for  DRF  
programs”。
这种支持通常需要一些特殊的操作来进行同步操作。

考虑内存模型  XC。
要求程序员或低级软件确保所有同步操作都在  FENCE  之前和之后,
如表
5.8  中所示。

通过围绕同步操作的  FENCE,
XC  支持  DRF  程序的  SC。
虽然证明超出了这项工作的范围, 但该结果背后的直觉来自上面讨论的表5.7和表5.8中的示例。

支持  DRF  程序的  SC  允许许多程序员使用  SC  而不是更复杂的  XC  规则来推理他们的程序,
同时, 受益于  XC  在  SC  上实现的任何硬件性能改进或简化。 问题是 而且不是总有问题吗? 保证  
DRF  的高性能(即, 没有将太多操作标记为同步) 可能具有挑战性。

‧  无法确定究竟哪些内存操作可以竞争, 因此必须标记为同步。 图5.4描述了一个执行,


其中核心  
C2  的存储应该被标记为同步 它确定  FENCE  是否实际上是必要的 只有当我们可以确定  
C1  的初始代码块是否没有停止时, 这当然是不可判定的。 不确定性可以通过在不确定是否需要  
FENCE  时添加  FENCE  来避免。这始终是正确的, 但可能会损害性能。 在极限情况下,
可以通过  
FENCE  包围所有内存操作, 以确保任何程序的  SC  行为。

‧  最后,
由于错误, 程序可能会出现违反  DRF  的数据竞争。
坏消息是, 在数据竞争之后, 执行可能
不再服从  SC,
迫使程序员对底层的宽松内存模型(例如, XC)
进行推理。好消息是,至少在第一
次数据竞争之前, 所有执行都将服从  SC,
从而允许仅使用  SC  推理进行一些调试[5]。

总之,
使用宽松记忆系统的程序员可以通过两种选择来推理他们的程序:

‧  他们可以使用规则直接推理模型做什么和不做什么(例如,
表5.5等)

Machine Translated by Google

72  5.放松记忆一致性

C1 C2

仅当核心  C1  执行“X  =  1”
时,核心  C2  的  
FENCE  F3  和  F4  才是必需的。
判断“X  =  1”
这段代码会
是否执行是不可判定的,
因为它需要解决不可判
停止吗? F3:
栅栏?
定的停机问题。
X  =  2  
F1:
栅栏 F4:
栅栏?
种族?
X  =  1
F2:
栅栏

图  5.4:  FENCE  的最佳放置是不确定的。

‧  他们可以插入足够的同步以确保没有数据竞争 仍然允许同步竞争 并使用相对简单的顺序


一致性模型来推理他们的程序, 该模型永远不会出现乱序执行内存操作的情况。

我们几乎总是推荐后一种“无数据争用的顺序一致性”
方法,
将前一种方法留给编写同步库或
设备驱动程序等代码的专家。

5.5  一些放宽模型概念

学术文献提供了许多可供选择的放松记忆模型和概念。 在这里,
我们从大量文献中回顾了一些松散的
记忆概念, 以提供一个基本的理解,但全面而正式的探索超出了本入门书的范围。
幸运的是,SC  for  
DRF  的用户(可能是大多数程序员)不必掌握这个困难部分中的概念。
第一次阅读时,
读者可能希望
略读或跳过本节。

5.5.1  发布一致性

在  Adve  和  Hill  提出“SC  for  DRF”
的同一个  ISCA  1990  会议上,Gharachorloo  等人。  [20]提出
发布一致性(RC)。 使用本章的术语, RC  的主要观察结果是用  FENCE  包围所有同步操作是过度的。
随着对同步的深入理解, 同步获取只需要一个后继的FENCE, 而同步释放只需要一个前置的FENCE。

对于表  5.4  的关键部分示例,可以省略  FENCE  F11、
F14、
F21  和  F24。
让我们关注“R11:
释放
(锁定)”。  FENCE  F13  很重要,
因为它在锁释放之前对临界区的加载  (L1i)  和存储  (S1j)  进行排
序。  FENCE  F14  可能是
Machine Translated by Google

5.5.一些宽松的模型概念  73

省略是因为如果核心  C1  的后续内存操作(表中未显示)
在  R11  版本之前提前执行,
则没有问题。

RC  实际上允许这些后续操作早在临界区开始时就执行, 与  XC  的  FENCE  相反,
它不允许
这样的排序。  RC  提供类似于  FENCE  的  ACQUIRE  和  RELEASE  操作, 但只在一个方向上而不
是像  FENCE  那样在两个方向上排序内存访问。 更一般地说, RC  只需要:  ACQUIRE ! 加载、
存储
(但不是加载、 存储!
获取)

加载, 存储!  RELEASE(但不是  RELEASE !
加载、
存储)和  SCQUIRE  
和  RELEASE  的排序:ACQUIRE !
收购收购! 发布发布!获取和释放! 发

5.5.2  因果关系和写原子性
在这里, 我们说明了松弛模型的两个微妙属性。 第一个属性, 因果关系, 要求“如果我看到它并告
诉你它, 那么你也会看到它。” 例如,考虑表5.9 , 其中核心  C1  执行存储  S1  以更新数据  1。 让核心
C2旋转直到它看到S1的结果(r1==NEW), 执行FENCE  F1, 然后执行S2更新data2。 类似地, 核
心  C3  在负载  L2  上旋转,
直到它看到  S2  (r2==NEW)  的结果, 执行  FENCE  F2,
然后执行  L3  以
观察存储  S1。 如果保证核心  C3  观察到  S1  done  (r3==NEW),
则因果关系成立。 另一方面, 如果  
r3  为  0,
则违反因果关系。

第二个属性,
写原子性(也称为存储原子性或多副本原子性), 要求一个核心的存储在逻
辑上同时被所有其他核心看到。  XC  根据定义是写原子的,
因为它的内存顺序  (<m)  指定存储在
内存中生效的逻辑原子点。
在此之前, 没有其他核心可以看到新存储的值。在这一点之后, 所有其
他核心必须看到新值或来自稍后存储的值, 但不能看到被存储破坏的值。根据  XC  的要求,写原子
性允许一个核心在其他核心看到它之前看到自己存储的值, 这导致一些人认为“写原子性” 是一
个糟糕的名字。

写原子性的必要但不充分条件是正确处理独立读取独立写入  (IRIW)示例。  IRIW  如表
5.10所示, 其中核心  C1  和  C2  分别存储  S1  和  S2。
假设核心  C3  的负载  L1  观察  S1  (r1==NEW),
核心  C4  的  L3  观察  S2  (r3==NEW)。
如果C3的L2加载0(r2==0), C4的L4加载0(r4==0) 呢?
前者意味着核心  C3  在看到  S2  之前看到存储  S1, 而后者意味着  C4  在  S1  之前看到  S2。 在这种
情况下, 存储  S1  和  S2  不仅被“重新排序”, 而且甚至不存在存储顺序, 并且违反了写原子性。 反
之则不然
Machine Translated by Google

74  5.  放松记忆的一致性

必然为真: 正确处理  IRIW  并不自动意味着存储原子性。
更多事实(可能会让您的头受伤并渴望  
SC、
TSO  或  DRF  的  SC):

‧  写原子性意味着因果关系。例如, 在表5.9中, 核心  C2  观察存储  S1,


执行  FENCE,
然后存储  
S2。通过写入原子性,这确保  C3  将存储  S1  视为已完成。

‧  因果关系并不意味着写原子性。
对于表5.10, 假设核心  C1  和  C3  是共享写缓冲区的多线程
核心的两个线程上下文。 假设核心  C2  和  C4  相同。
让C1把S1放在C1‑C3的写缓冲区中, 这样
它只被C3的L1观察到。同样,
C2  将  S2  放入  C2‑C4  写缓冲区, 因此  S2  只被  C4  的  L3  观察
到。
在任一存储离开写入缓冲区之前, 让  C3  执行  L2  并让  C4  执行  L4。此执行违反了写原子
性。
然而,使用表5.9中的示例,可以看出此设计提供了因果关系。

表  5.9:
因果关系:
如果我看到一家商店并告诉你它,
你一定也看到了吗?

核心C1 核心C2 评论
S1:
数据  1  =  新的; /*  最初,
data1  &  data2  =  0  */
L1:
r1=数据1;
B1:
如果(r1≠NEW)
转到L1;
F1:
栅栏

S2:
数据  2  =  新的;
L2:
r2=数据2;
B2:
如果(r2≠NEW)
转到L2;
F2:
栅栏
L3:
r3=数据1; /*  r3==新的?  */

表  5.10:  IRIW  示例:
商店必须按某种顺序排列吗?

核心C1 核心C2 核心C3 核心C4

S1:
数据  1  =  新的;  S2:
数据  2  =  新的; /*  最初,
data1=data2=0  */
L1:
r1=数据1; /*  新的  */
F1:
栅栏 L3:
r3=数据2; /*  新  */  L2:  r2  =  
data2; /*  新的?  */  F2:  栅栏
L4:
r4=数据1; /*  新的?  */
Machine Translated by Google

5.6.放松记忆模型案例研究  75

最后,
XC  内存模型既是存储原子的又保持因果关系。
我们之前认为  XC  是存储原子的。  XC  保
持因果关系,
因为存储原子性意味着因果关系。

5.6  放松记忆模型案例研究
在本节中, 我们将介绍两个内存模型案例研究:
RISC‑V  的  RVWMO(RISC‑V  弱内存顺序)
和  IBM  的  
Power  Memory  模型。

5.6.1  RISC‑V  弱内存指令  (RVWMO)
RISC‑V  实现了一个内存模型  RVWMO, 1可以理解为  Release  Consistency  (RC)  和  XC  的混合体。
与  XC  类似,RVWMO  是根据全局内存顺序(所有内存操作的总顺序) 定义的, 并且具有  FENCE  指令的
多种变体。 与  RC  类似,加载和存储可以携带注释: 加载指令可以携带  ACQUIRE  注释, 存储指令可以携
带  RELEASE  注释, RMW  指令可以使用  RELEASE、
ACQUIRE  或同时使用  RELEASE  和  ACQUIRE  进
行注释。 在下文中, 我们将提供  RVWMO  内存模型的摘要, 解释  RVWMO  如何结合  XC  和  RC  的各个方
面。 我们还将讨论  RVWMO  如何在某些方面略强于  XC  而在其他方面较弱。

发布/获取订单。 有两种类型的  ACQUIRE  注释:  ACQUIRE‑RCP  C同样,有两种类型的  RELEASE  注
2个

释:  RELEASE‑RCP  C
和  
和  
ARCQUIRE‑RCSC 。
ELEASE‑RCSC 。
加载(存储) 可以携带任一  
而  RMW   ACQUIRE(RELEASE)
只能携带RCSC注释。 注释保留以下顺序:注释,

‧  获取!  Load,Store(ACQUIRE指ACQUIRE‑RCSC和ACQUIRE‑RCP  C )

‧  加载、
存储!  RELEASE(RELEASE同时指RELEASE‑RCSC和RELEASE‑RCP  C )

‧  发布‑RCSC !
收购‑RCSC

FENCE  命令。  RVWMO  有多种  FENCE  指令变体。 有一个强大的  FENCE  指令,
FENCE  RW,RW,3 ,它
与  XC  中的一样, 强制执行  Load,Store ! 加载, 存储订单。 此外,
还有其他五个非平凡的组合: FENCE  
RW, W; 围栏  R,RW;围栏  R,  R;栅栏  W,W;和  FENCE.TSO。
栅栏  R,R,  对于

1RISC‑V  还指定了一种称为  zTSO  的  TSO  变体,
本节未讨论。
2PC  用于处理器一致性, SC  用于顺序一致性。  PC(第4.5  节)是  TSO  的不太正式的前身,
没有  TSO  的写原子性属性。
3R  表示读取(加载), W  表示写入(存储)。
Machine Translated by Google

76  5.放松记忆一致性

例如,强制加载!
仅加载排序。  FENCE.TSO  强制加载!
加载,
存储!
存储和加载!
商店订购但不是商
店!
加载。

表  5.11:  RVWMO:
FENCE  和  RELEASE‑ACQUIRE  顺序确保  r1  和  r2  都不能读取  0

核心C1 核心C2 评论
S1:
RELEASE‑RCSC  x  =  NEW; S2:
y  =  新; 最初  x=y=0
L1:
ACQUIRE‑RCSC  r1  =  y; 围栏  RW,  RW;
L2:
r2=x;

一个例子。 表5.11显示了同时包含  RELEASE‑ACQUIRE  和  FENCE  排序的示例。  S1 !
由于前者和  
S2,
L1  在核心  C1  中强制执行!
由于后者, L2  在核心  C2  中强制执行。 因此,该组合确保  r1  和  r2  都
不能读取  0。

表  5.12:
地址相关性导致的排序。  r1=&data2  和  r2=0  可以吗?

核心C1 核心C2 评论
S1:
数据  2  =  新的; L1:  r1  =  指针; 初始指针=&data1,
data1=0
栅栏  W,W
S2:  指针  =  &data2; L2:
r2=*r1;

表  5.13:
数据相关性导致的排序。  r1  和  r2  能读出  42  吗?

核心C1 核心C2 评论
L1:  r1  =  x; L2:  r2  =  y; 最初  x=y=0
S1:
y  =  r1; S2:
x  =  r2;

依赖诱导的排序。  RVWMO  在某些方面略强于  XC。 地址、数据和控制相关性可以在  RVWMO  中
引起内存排序, 但在  XC  中不会。
考虑表  5.12  中显示的示例。 在这里, 核心C1将NEW写入data2, 然后设置指针指向data2的位置。  
(两个商店  S1  和  S2  通过  FENCE  W,W  指令排序)。 在核心  C2  中, L1  将指针的值加载到  r1  中,
然后加载  L2  解引用  r1。 尽管两个负载  L1  和  L2  没有明确排序, 但  RVWMO  隐式强制执行  L1 !  
L2,
因为  L1  和  L2  之间存在地址依赖关系: L1  生成的值被  L2  取消引用。

考虑表  5.13  中显示的示例,
也称为负载缓冲。
让我们假设  x  和  y  最初都是  0。
可以允许  r1  
和  r2  都读取任意值(比如
Machine Translated by Google

5.6.放松记忆模型案例研究  77

42)、
凭空? 有点令人惊讶的是, XC  并不禁止这种行为。
因为  L1  和  S1  之间没有  FENCE,
L2  和  S2  之
间也没有  FENCE,
所以  XC  不强制执行任何一个负载! 门店订单。 这可能导致执行:

‧  S1  预测  L1  将读取  42,
然后推测性地将  42  写入  y,

‧  L2  将  42  从  y  读入  r2,

‧  S2  将  42  写入  x,
并且

‧  L1  将  x  中的  42  读入  r1,
从而使初始预测“正确”。

然而,
RVWMO  通过隐含地强制执行Load!Store  排序(L1 !  S1  和  L2 !  S2)
来禁止这种行为,

为每个加载和存储之间存在数据依赖性: 每个加载读取的值被写入通过以下商店。

以类似的方式,
RVWMO  还隐式地强制执行加载和后续存储之间的排序, 该排序取决于加载的
控制。
这是为了防止因果关系循环, 例如表5.14  中所示,
其中存储能够影响先前加载读取的值,
该值决
定存储是否必须执行。

表  5.14:
控制依赖性诱导排序。  r1能不能读到42,
导致S1(间接)
影响自己的执行?

核心C1 核心C2 评论
L1:
r1=x; L2:  r2  =  y; 最初  x  =  y  =  0
B1:
如果r1≠42返回;
S1:
y=42; S2:  x=  r2;

值得注意的是,以上所有的依赖都是指句法依赖而不是语义依赖, 即是否存在任何依赖是寄存
器标识的函数,而不是实际值。除了地址、数据和控制之外,
RVWMO  还强制执行“管道依赖性” 以反映
大多数处理器管道实现的行为; 对此的讨论超出了本入门书的范围, 读者可以参考  RISC‑V  规范[31]。

相同的地址排序。 回想一下,XC  维护访问同一地址的  TSO  排序规则。 与  XC  一样,


RVWMO  也强制加
载!
商店, 商店! 商店订购,
不强制商店! 将订单加载到同一地址。 与  XC  不同, RVWMO  不强制加载! 在所
有情况下将订单加载到同一地址。 它仅在以下情况下强制执行: (a)  两个加载之间没有存储到同一地
址,
以及  (b)  两个加载返回由不同存储写入的值。 有关这种微妙之处的详细讨论, 请读者参阅  RISC‑V  
规范[31]。
Machine Translated by Google

78  5.放松记忆一致性

RMW。  RISC‑V  支持两种类型的  RMW:
原子内存操作  (AMO)  和加载保留/存储条件  (LdR/StC)。  
AMO  来自单个指令(例如, 取指和递增), 而  LdR/StC  实际上由两条独立的指令组成: LdR  将值和预
留带入内核, 而  StC  只有在预留完成时才会成功仍然举行。 两种类型的  RMW  的原子性语义略有不同。

与  XC  RMW  类似,
如果加载和存储在全局内存顺序中连续出现, 则称  AMO  是原子的。  LdR/StC  较
弱。 假设  LdR  读取  store  s  产生的值;
只要在全局内存顺序中  s  和  StC  之间的相同地址没有存储,
LdR/StC  就被认为是原子的。

概括。
总之,RVWMO  是最近出现的一种结合了  XC  和  RC  方面的松弛内存模型。
对于详细的散文和正
式规范,读者可以参考  RISC‑V  规范[31]。

5.6.2  IBM  POWER
IBM  Power  实现了  Power  内存模型[23]  (特别参见第二本书的第1  章第  4.4  节和附录  B)。
我们
试图在这里给出  Power  内存模型的要点, 但我们建议读者参阅  Power  手册以获得明确的介绍, 尤其
是对于  Power  编程。 我们没有为  SC  提供像表5.5这样的排序表, 因为我们不确定我们能否正确指定
所有条目。 我们只讨论正常的可缓存内存(“启用内存一致性”, 禁用“要求直写”和禁用“缓存禁
止”) 而不是  I/O  空间等。PowerPC  [24]  代表当前  Power  模型的早期版本。
第一次阅读本入门读物
时, 读者可能希望略读或跳过本节; 这个内存模型比本入门中目前介绍的模型复杂得多。

Power  提供了一个轻松的模型,
它在表面上与  XC  相似,
但具有以下重要差异。

首先, Power  中的存储是针对(wrt) 其他内核执行的, 而不是针对内存执行的。


当核心  C2  对同一地址的任何加载将看到新存储的值或来自稍后存储的值, 但不会看到被存储破坏的
先前值时, 核心  C1  的存储将“执行” 核心  C2。  Power  确保如果核心  C1  使用  FENCE  在  S2  和  S3  
之前对存储  S1  进行排序, 那么这三个存储将以相同的顺序与其他每个核心  Ci  一起执行。 然而, 在没
有  FENCE  的情况下, 核心  C1  的存储  S1  可以对核心  C2  执行,
但尚未对  C3  执行。 因此, Power  不能
保证像  XC  那样创建总内存顺序  (<m)。

其次, Power  中的一些  FENCE  被定义为累积的。 让核心  C2  执行一些内存访问  X1,  X2, .一个  


FENCE, 然后一些内存访问  Y1, . . ,
Y2,...。
令设  X  =  {Xi}  并设  Y  =  {Yi}。  (Power  手册分别称这些组为  A  和  B。)
定义累积意味着三件事: (a) 将在  FENCE  之前排序的其他核心的内存访问添加到集合  X  (例如, 如
果在  C2  的  FENCE  之前对核心  C2  执行  S1, 则将核心  C1  的存储  S1  添加到  X);  (b)  将其他内核
的内存访问添加到集合  Y  中
Machine Translated by Google

5.6.放松记忆模型案例研究  79

在  FENCE  之后通过数据依赖、控制依赖或另一个  FENCE;  (c)  向后递归地应用  (a)(例如,对于具
有先前使用内核  C1  订购的访问权限的内核) 并向前递归地应用  (b)。  (XC  中的  FENCE  也是累积
的, 但它们的累积行为是由  XC  的总内存顺序自动提供的, 而不是由  FENCE  专门提供的。)

第三,
Power有三种FENCE(I/O  memory还有更多),
而XC只有一种FENCE。

‧  SYNC  或  HWSYNC(“HW”
表示“重量级”,
“SYNC”
表示“同步
nization”)  在所有访问  Y  之前对所有访问  X  进行排序,
并且是累积的。

‧  LWSYNC(“LW”的意思是“轻量级”) 在  Y  中加载之前订购  X  中的负载,
在  Y  中存储之前
订购  X  中的负载,
并在  Y  中存储之前订购  X  中的存储。 LWSYNC  是累积的。
请注意,
LWSYNC  不会在Y  中加载之前对  X  中的存储进行排序。

‧  ISYNC(“I”
的意思是“指令”) 有时用于从同一个内核订购两个负载, 但它不是累积的,尽管
它的名字, 它不是像  HWSYNC  和  LWSYNC  那样的  FENCE,
因为它订购指令而不是内存访问。
由于这些原因, 我们在示例中不使用  ISYNC。

第四,
在某些情况下, 即使没有  FENCE,
Power  指令访问也是如此。
例如,如果负载L1获得了用
于计算后续负载L2的有效地址的值,则Power将负载L1排在负载L2之前。 另外,
如果负载L1获得用于
计算后续存储S2的有效地址或数据值的值, 则Power命令负载L1在存储S2之前。

表5.15说明了  Power  的  LWSYNC  的运行情况。核心  C1  执行  LWSYNC  以在  S3  之前对数据
存储  S1  和  S2  进行排序。
请注意, LWSYNC  不会对存储  S1  和  S2  进行排序,
但这不是必需的。  
LWSYNC  在这里提供了足够的顺序, 因为它在  Y  (S3)  中的存储之前对  X(S1  和  S2)
中的存储进行排
序。类似地, 核心  C2  在其条件分支  B1  之后执行  LWSYNC, 以确保加载  L1  在加载  L2  和  L3  执行之
前完成, 并且  r1  分配给  SET。
不需要  HWSYNC,因为两个内核都不需要在加载前对存储进行排序。

表5.16说明了  Power  的  HWSYNC  在  Dekker  算法的关键部分上的作用。
HWSYNC  确保核心  C1  的存储  S1  在加载  L1  之前,核心  C2  的存储  S2  在加载  L2  之前。
这可以防止
执行以  r1=0  和  r2=0  终止。
使用  LWSYNC  不会阻止此执行, 因为  LWSYNC  不会在稍后加载之前对
较早的存储进行排序。
如表  5.17  中所述,  Power  的  LWSYNC  可用于使表5.9中的因果关系示例表现得合理(即,
r3  始终设置为  NEW )。  LWSYNC  F1  仅在加载  L1  看到  data1  的新值后执行, 这意味着存储  S1  
已在核心  C2  中执行。  LWSYNC  F1  通过累积特性将  S1  排在  S2  之前, 相对于核心  C2。

LWSYNC  F2  仅在加载  L2  看到  data2  的新值后执行,
这意味着存储  S2  已在核心  C3  中执行。
累积
属性还确保存储  S1  已在  S2  之前通过核心  C3  执行(由于  LWSYNC  F1)。 最后,
LWSYNC  F2  命令
在  L3  之前加载  L2,
确保  r3  获得值  NEW。
Machine Translated by Google

80  5.  放松记忆的一致性

表  5.15:
为  LWSYNC  供电以确保  r2  和  r3  始终获得新的

核心C1 核心C2 评论

S1:
数据  1  =  新的; /*  最初,
data1  &  data2  =  0  &  fl  ag  ≠  SET  */
S2:
数据  2  =  新的;
F1:
长同步 /*  在  S3  之前确保  S1  和  S2  */

S3:
标志=设置; L1:  r1  =  fl  ag; /*  自旋循环:
L1  &  B1  可能重复多次  */
B1:
如果(r1≠SET)
转到L1;
F2:
LWSYNC /*  确保  B1  在  L2  和  L3  之前  */

L2:
r2=数据1;
L3:
r3=数据2;

表  5.16:
确保  r1  和  r2  均未设置为  0  的电源  HWSYNC

核心C1 核心C2 评论

S1:  x  =  新的; S2:
y  =  新; /*  最初,
x  =  0  &  y  =  0  */
F1:
高速同步 F2:
硬件同步 /*  确保  S1  在  Li  之前  for  i  =  1,2  */

L1:  r1  =  y; L2:  r2  =  x;

表  5.17:
为  LWSYNC  供电以确保因果关系(r3  ==  NEW)

核心C1 核心C2 核心C3

S1:
数据  1  =  新的; /*  最初,
data1  &  data2  =  0  */
L1:
r1=数据1;

B1:
如果(r1≠NEW)
转到L1;
F1:
长同步

S2:
数据  2  =  新的;
L2:
r2=数据2;

B2:
如果(r2≠NEW)
转到L2;
F2:
LWSYNC

L3:
r3=数据1; /*  r3  ==  新的?  */

如表  5.18  所示,  Power  的  HWSYNC  可用于使表5.10的独立读取独立写入示例  (IRIW)表现得合理(即,
不允许结果  r1==NEW、r2==0、r3==NEW  和  r4=  =0).使用  LWSYNC  是不够的。
考试‑
Machine Translated by Google

5.7.进一步阅读和商业松弛记忆模型  81

例如,
核心  C3  的  F1  HWSYNC  必须在核心  C3  的加载  L2  之前累积排序核心  C1  的存储  S1。

查看  Power  内存模型的另一种方法是指定使  Power  表现得像  SC  所需的  FENCE。
通过在每
对内存访问指令之间插入一个  HWSYNC, 可以将  Power  限制为  SC  执行。
4以上是一个思想实验, 绝
对不是在  Power  上获得良好性能的推荐方法。

表  5.18:
使用  IRIW  示例为  HWSYNC  供电

核心C1 核心C2 核心C3 核心C4

S1:
数据  1  =  新的;  S2:
数据  2  =  新的; /*  最初,
data1  =  data2  =  0  */
L1:
r1=数据1; /*  新的  */ L3:
r3=数据2; /*  新的  */
F1:
高速同步 F2:
硬件同步
L2:
r2=数据2; /*  新的?  */  L4:  r4  =  data1; /*  新的?  */

5.7  进一步阅读和商业松弛记忆模型

5.7.1  学术文献

以下是大量放松记忆一致性文献中的一些亮点。 在最早开发的松弛模型中, 有  Dubois  等人的模


型。  [18]弱排序。  Adve  和  Hill  将弱排序归纳为程序员使用“SC  for  DRF”  [3,  4]严格必需的顺
序。
Gharachorloo  等人。  [20]开发了发布一致性, 以及“适当的标签” (可以看作是“SC  for  DRF” 的
概括) 和允许同步操作遵循  TSO  (“RCPC”) 的模型。  Adve  和  Gharachorloo  [2]撰写了开创性
的记忆模型教程, 总结了  20  世纪  90  年代中期的最新技术。

据我们所知,
Meixner  和  Sorin  [29]是第一个证明松弛内存模型正确性的人,
该模型通过使用
受特定规则约束的重新排序单元分离核心和高速缓存一致性内存系统来实现。

5.7.2  商业模式

除了  Power  [23]  之外,
商业宽松内存模型还包括  Alpha  [33]、  SPARC  RMO  [34]和  ARM  [9、
10 ]。

Alpha  [33]在很大程度上已经失效, 但是, 像  XC  一样,


假定了一个总的内存顺序。  Alpha  保
留了一定的重要性, 因为它对  Linux  同步有很大的影响, 因为  Linux  在  Alpha  [28]上运行(特别参
见第  12  章和附录  C)。  McKenney  [28]指出  Alpha

当使用多个访问大小[19]  时,
4FENCE  无法在  IBM  Power  上恢复  SC。
Machine Translated by Google

82  5.  放松记忆的一致性

即使第一个为第二个提供了有效地址,
也没有订购两个负载。
更一般地说,
McKenney  的在线书籍是有关  Linux  同步
及其与内存一致性模型交互的良好信息来源。
阿尔格拉夫等。  [8]提供了一个正式的和可执行的模型。

SPARC  Relaxed  Memory  Order  (RMO)  [34]也像  XC  一样提供总内存顺序。
尽管  SPARC  允许操作系统在  
TSO、
PSO  和  RMO  之间选择内存模型,
但所有当前的  SPARC  实现在所有情况下都使用  TSO。  TSO  是  PSO  和  
RMO  的有效实现,
因为它更严格。

ARMv7  [9,  10]提供了一种在本质上与  IBM  Power  相似的内存模型。
与  Power  一样,
它似乎不能保证总内存
顺序。
与  Power  一样,
ARM  也有多种  FENCE,
包括可以对所有内存访问进行排序或仅对存储进行排序的数据内存屏
障和指令同步屏障(如  Power  的  ISYNC),
以及用于  I/O  操作的其他  FENCE。
对于  ARMv8  [11],  ARM  切换到提
供总内存顺序的多副本原子内存模型。
该模型在本质上类似于  RISC‑V  的  RVWMO,
5允许使用  ACQUIRE  和  RELEASE  
注释对加载和存储进行注释。

5.8  比较内存模型
5.8.1  松弛记忆模型如何相互关联以及  TSO  和  SC?

回想一下,
如果所有  X  执行(实现)
也是  Y  执行(实现) ,
则内存一致性模型  Y  严格来说比内存一致性模型  X  更宽
松(更弱),
但反之则不然。
也有可能两个内存一致性模型是不可比的,
因为它们都允许执行(实现)
被另一个排除。

图5.5重复了上一章中的一个图,
其中  Power  替换了之前未指定的  MC1,
而  MC2  可能是  Alpha、
ARM、
RMO  
或  XC。
他们如何比较?
‧  Power  比TSO  更宽松,
TSO  比SC  更宽松。

‧  Alpha、
ARM、
RMO  和XC  比TSO  更宽松,
而TSO  又比SC  更宽松。

‧  Power  被认为与Alpha、
ARM、
RMO  和XC  无可比拟,
直到有人证明其中一个比另一个更放松或两者是等效的。

Mador‑Haim  等人。  [26]开发了一种用于比较内存一致性模型(包括  SC、
TSO  和  RMO)
的自动化技术,

他们没有考虑  ARM  或  Power。
ARM  和  Power  可能是等效的,
但我们等待证明。

5.8.2  松弛模型有多好?
正如上一章所讨论的,
一个好的记忆一致性模型应该具备  Sarita  Adve  的  3P  加上我们的第四  P。

5但在指定  RVWMO  之前定义
Machine Translated by Google

5.9.高级语言模型  83

SC SC

力量 TSO MC2 力量 TSO MC2

全部 全部

(a)  处决 (b)  实施(与(a)
相同)

图  5.5:
比较内存一致性模型。

‧可编程性:
对于那些使用“SC  for  DRF”
的人来说,
宽松的模型可编程性是可以接受的。
深入理解
松弛模型(例如,
编写编译器和运行时系统) 是困难的。

‧性能:
宽松的内存模型可以提供比  TSO  更好的性能,
但是
许多核心微架构的差异较小。

‧可移植性:
保持在“SC  for  DRF”
内的移植是可管理的。
突破放松模型的极限,
尤其是那些无与伦
比的模型,是很难的。

‧精确:
许多宽松模型只是非正式定义的。
此外,
正式定义
宽松的模特往往很迟钝。

底线是⋯⋯没有简单的底线。

5.9  高级语言模型
前两章和本章到目前为止都在讨论硬件和低级软件之间接口的内存一致性模型,
讨论  (a)  软件作者应
该期望什么以及  (b)  硬件实现者可以做什么。

为高级语言  (HLL)  定义内存模型也很重要, 指定  (a)  HLL  软件作者应该期望什么以及  (b)  编译
器、
运行时系统和硬件的实现者可以做什么。 图5.6说明了  (a)  高级和  (b)  低级内存模型之间的区别。

因为许多  HLL  出现在一个主要是单线程的世界中, 所以它们的规范省略了内存模型。  (您还
记得  Kernighan  和  Ritchie  [25]  中的一个吗?)
Java  可能是第一个具有内存模型的主流语言, 但第
一个版本存在一些问题[30]。
然而, 最近, 内存模型已为  Java  [27]和CCC  [14]  重新指定。
幸运的是,这两个模型的基⽯都是一
个熟悉的模型 DRF  [3]  的  SC 部分由所有三篇论文的共同作者  Sarita  Adve  提供。 允许同步竞争
Machine Translated by Google

84  5.  放松记忆的一致性

C++程序 Java程序
(A)

Java  编译器
C++  编译器
低级程序 Java运行时
(乙)

硬件

图  5.6:  (a)  高级语言  (HLL)  和  (b)  硬件内存模型。

但不是数据竞争, 程序员必须在变量可能竞争时将变量标记为同步, 使用诸如atomic  之类的关键


字,
或者使用  Java  的类似监视器的同步方法隐式创建同步锁。
在所有情况下,
只要无数据竞争的
程序服从  SC,
实现就可以自由地重新排序引用。

特别是, 实现可以重新排序或消除同步访问之间的内存访问。 图5.7说明了一个例子。 图


5.7(a)  显示了  HLL  代码,
图5.7(b)  和  (c)  显示了内核  C1  上的执行, 两者都没有寄存器分配, 并且
变量  A  分配在寄存器  r1  中, 以便负载  L3  被重新排序并与负载  L1  合并。 这种重新排序是正确的,
因为对于“SC  for  DRF”, 没有其他线程“可以查看”。 除了寄存器分配, 许多(如果不是大多
数的话) 编译器和运行时优化 例如常量传播、 公共子表达式消除、 循环裂变/融合、 循环不变代码
移动、 软件流水线和指令调度 可以重新排序内存访问。 因为“SC  for  DRF”
允许这些优化,编译
器和运行时系统可以生成性能与单线程代码相当的代码。

如果  HLL  程序有意或无意地出现数据竞争怎么办? 或者, 如果专业程序员想要对同步操作


施加最小排序(甚至没有排序), 利用应用程序知识怎么办? 在这种情况下, 另一个线程可以观察
到非  SC  执行。对于图5.7,核心  C2(未显示) 可以更新  A  和  C,并让核心  C1  观察对  C  而不是  A  
的更新, A  已被寄存器分配。 此执行在  HLL  级别违反了  SC。

更一般地说, 对于哪些线程可以观察到数据竞争或非  SC  同步操作的限制是什么? 具体来


说,
Java  需要为所有程序提供安全保证;  C++出于性能考虑, 支持多种弱于SC的同步操作(称
为低级原子)。 由于这些原因,Java  和  C++  必须在所有情况下指定行为,以达到以下目标:

1.  允许对高性能  DRF  程序进行所有优化,
Machine Translated by Google

5.9.高级语言模型  85

核心  C1  的替代品

(a)  高级语言
B  =  2*A;
D  =  C  ‑  A;

程序顺序  (<p) 内存顺序  (<m)
(b)  幼稚
L1:r1  =  A; L1:  r1  =  A
X1:  r2  =  2*r1; S1:  B  =  r2
S1: B  =  r2;
L2:  r3  =  C L2:  r3  =  C

L3:r1  =  A; L3:
r1  =  A
X2:  r2  =  r3  ‑  r1;
S2:
D  =  r2
S2: D=r2;

(c)  寄存器分配
L1,  L3:  r1  =  A;
X1:  r2  =  2*r1; L1:  r1  =  A
S1: B  =  r2; S1:  B  =  r2
L2:  r3  =  C;
L2:  r3  =  C
/*  L3:  r1  =  A;移动  */
X2:  r2  =  r3  ‑  r1; L3:
r1  =  A
S2: D=r2;
S2:
D  =  r2

图  5.7:
寄存器分配影响内存顺序。

2.  明确指定所有程序允许的行为,
以及

3.  使这个明确的规范变得简单。

根据我们的判断, 2005  年的  Java  内存模型取得了实质性进展,
可以说在  (2)  上取得了成
功,
主要在  (1)  上取得了成功 它不允许一些编译器优化  [ 32] 但在  (3)  上没有。 幸运的是, 大多
数程序员可以使用“SC  for  DRF” 而不会遭受  Java  内存模型“暗角”
的后果。 然而, 编译器和运
行时软件的作者必须在某种程度上面对它们。 考虑这个必须理解的“黑暗角落”: 无论优化如何,
对地址  A  的加载必须始终返回一个在某个时间(可能在初始化时) 存储到地址  A  的值, 而不是一
个“凭空” 的值, 如图所示
Machine Translated by Google

86  5.放松记忆一致性

见表5.13。
不幸的是,
这并不是必须面对的唯一的复杂性例子[12]。

满足以上所有三个要求仍然是一个悬而未决的问题,
并且正在积极
在  C++  的背景下进行研究[13、
17 ]。

闪回测验问题  5:
相对于高级语言一致性模型(例如,
Java)
编写正确同步代码的程序员不需要考虑
体系结构的内存一致性模型。 对或错?

答:
这取决于。对于典型的应用程序程序员,答案是正确的,因为他们的程序按预期运行(SC  是预期
的)。对于编译器和运行时系统的程序员来说,答案是否定的。

因此, Java  和CCC等  HLL采用“SC  for  DRF”
的宽松内存模型方法。 当这些HLL  运行在硬件上
时,
硬件的内存模型是否也应该放宽? 一方面, (a)  宽松的硬件可以提供最大的性能, 并且  (b)  编译器和
运行时软件只需要将  HLL  的同步操作转换为特定硬件的低级同步操作和  FENCE  以提供必要的排序。
另一方面,(a)  SC  和  TSO  可以提供良好的性能, 并且  (b)  编译器和运行时软件可以从无与伦比的内存
模型中生成没有  FENCE  的更多可移植代码。 尽管争论尚未解决, 但很明显宽松的  HLL  模型不需要宽
松的硬件。

5.10  参考文献
[1](不要)
自己动手:
弱记忆模型。  http://diy.inria.fr/

[2]  SV  Adve  和  K.  Gharachorloo。共享内存一致性模型:
教程。  IEEE计算机,  29(12):66–76,
1996  
年  12  月。DOI: 10.1109/2.546611。  81

[3]  SV  Adve  和  MD  Hill。
弱序 新定义。 在过程中。 第  17  届计算机体系结构国际研讨会,
第  2‑14  页,
1990  年  5  月。DOI:  10.1109/isca.1990.134502。  68,  81,  83

[4]  SV  Adve  和  MD  Hill。
四种共享内存模型的统一形式化。  IEEE并行和分布式系统汇刊,  1993  年  
6  月。DOI: 10.1109/71.242161。  81

[5]  SV  Adve、
MD  Hill、
BP  Miller  和  RHB  Netzer。检测弱内存系统上的数据竞争。 在过程中。 第  18  届
年度国际计算机体系结构研讨会, 第  234–43  页,
1991  年  5  月。
DOI:
10.1145/115952.115976。  
71
Machine Translated by Google

5.10.参考文献  87

[6]  J.  Alglave、
L.  Maranget、
S.  Sarkar  和  P.  Sewell。
弱记忆模型中的栅栏。
在过程中。 国际计算机辅助验证会议,  2010  年  7  月。 DOI:  10.1007/978‑3‑642‑14295‑6_25。

[7]  J.  Alglave、
L.  Maranget、
S.  Sarkar  和  P.  Sewell。  Litmus:针对硬件运行测试。 在过程中。 系
统构建和分析工具和算法国际会议,  2011  年  3  月。 DOI: 10.1007/978‑3‑642‑19835‑9_5。

[8]  J.  Alglave、L.  Maranget、
PE  McKenney、
A.  Parri  和  A.  Stern。
吓坏小孩子和令人不安的大
人: Linux  内核中的并发性。 在ASPLOS,  2018  年。
DOI:
10.1145/3296957.3177156。  82

[9]  手臂。  ARM  体系结构参考手册、 ARMv7‑A  和  ARMv7‑R  版勘误表
标记。  2011年1  月  13  日下载。
81、82

[10]  手臂。  ARM  v7A+R  体系结构参考手册。
可从  ARM  Ltd.  81、
82获得

[11]  手臂。  ARM  v8  体系结构参考手册。
可从  ARM  Ltd.  82获得

[12]  M.  Batty、
K.  Memarian、
K.  Nienhuis、
J.  Pichon‑Pharabod  和  P.  Sewell。
编程语言并发语
义问题。 在员工持股计划中,  2015.DOI  : 10.1007/  978‑3‑662‑46669‑8_12。  86

[13]  M.  Batty、
S.  Owens、
S.  Sarkar、
P.  Sewell  和  T.  Weber。
数学化  C++  并发性。
在POPL,  2011  年。
DOI:
10.1145/1926385.1926394。  86

[14]  H.‑J。  Boehm  和  SV  Adve。  CCC并发内存模型的基础。
在过程中。
编程语言设计与实现会
议,  2008  年  6  月。
DOI:
10.1145/1375581.1375591。  83

[15]  HW  Cain  和  MH  Lipasti。
内存排序:一种基于价值的方法。 在过程中。 第  31  届年度计算机体系
结构国际研讨会,  2004  年  6  月。 DOI:  10.1109/isca.2004.1310766。  58

[16]  L.  Ceze、J.  Tuck、
P.  Montesinos  和  J.  Torrellas。  BulkSC:
顺序一致性的批量执行。 在过程
中。 第  34  届年度计算机体系结构国际研讨会,  2007  年  6  月。 DOI:
10.1145/1250662.1250697。  
68

[17]  S.  Chakraborty  和  V.  Vafeiadis。
使用事件结构接地  thin‑air  reads。
在POPL  中,  2019.DOI  
:10.1145/3290383。  86

[18]  M.  Dubois、
C.  Scheurich  和  FA  Briggs。
多处理器中的内存访问缓冲。
在过程中。 第  13  届计算机体系结构国际研讨会, 第  434‑42  页,
1986  年  6  月。
DOI:
10.1145/285930.285991。  81
Machine Translated by Google

88  5.  放松记忆的一致性

[19]  S.  Flur、
S.  Sarkar、
C.  Pulte、
K.  Nienhuis、
L.  Maranget、
KE  Gray、
A.  Sezgin、
M.  Batty  和  P.  Sewell。
混合
大小并发: ARM、POWER、 C/C++11  和  SC。
在过程中。 第  44  届  ACM  SIGPLAN  编程语言原理研讨会,
第  
429–442  页,2017  年  1  月。DOI:10.1145/3009837.3009839。  81

[20]  K.  Gharachorloo、
D.  Lenoski、
J.  Laudon、
P.  Gibbons、
A.  Gupta  和  J.  Hennessy。
可扩展共享内存中的内存一致性和事件排序。 在过程中。 第  17  届年度计算机体系结构国际研讨会,
第  15‑26  
页,
1990  年  5  月。
DOI:  10.1109/isca.1990.134503。  72,  81

[21]  C.  Gniady、 B.  Falsafi  和  T.  Vijaykumar。
是  SC  C  ILP  D  RC  吗?
在过程中。第  26  届计算机体系结构国际研
讨会, 第  162‑71  页, 1999  年  5  月。 DOI:  10.1109/isca.1999.765948。  58

[22]  医学博士希尔。
多处理器应该支持简单的内存一致性模型。  IEEE计算机,  31(8):28–34,
1998  年  8  月。
DOI:
10.1109/2.707614。  66

[23]  IBM。  Power  ISA  版本  2.06  修订版B。  http://www.power.org/resources/downloads/  
PowerISA_V2.06B_V2_PUBLIC.pdf,
2010  年  7月。
78、
81

[24]  IBM  公司。  E  册:
增强型  PowerPC  架构,
0.91  版,  2001  年  7  月  21  日。
78

[25]  BW  Kernighan  和  DM  Ritchie。  C  程序设计语言,
第  2  版,
Prentice  Hall,
1988.  83

[26]  S.  Mador‑Haim、
R.  Alur  和  MMK  Martin。
为对比内存一致性模型生成⽯蕊测试。
在过程中。
第  22  届计算机
辅助验证国际会议,  2010  年  7  月。
DOI:
10.1007/978‑3‑642‑14295‑6_26。  82

[27]  J.  Manson、 W.  Pugh  和  SV  Adve。  Java内存模型。 在过程中。 第  32  届编程语言原理研讨会,  2005  年  


1  月。http://dx.doi.org/10。
1145/1040305.1040336.1040336  DOI: 10.1145/1040305.1040336。  83

[28]  PE  麦肯尼。并行编程难吗? 如果难, 你能做些什么?  http://kernel.org/pub/linux/kernel/people/


paulmck/perfbook/perfbook。  2011.01.%05a.pd,  2011.  81

[29]  A.  Meixner  和  DJ  Sorin。
缓存一致性多线程计算机体系结构中内存一致性的动态验证。
在过程中。
可靠系
统和网络国际会议,
第  73‑82  页,
2006  年  6  月。
DOI:
10.1109/dsn.2006.29。  81

[30]  W.普格。  Java  内存模型存在致命缺陷。 并发性: 实践与经验,  12(1):1–11,  2000。 DOI  :


10.1002/1096‑9128(200005)12:6%3C445::aid  cpe484%3E3.0.co;2‑a。  83
Machine Translated by Google

5.10.参考文献  89

[31]  RISC‑V  指令集手册, 第一卷: 非特权ISA  https://github.com/riscv/riscv‑isa‑manual/


releases/download/draft‑20190521‑21c6a14/riscv‑spec.pdf  DOI:
10.21236/
ada605735。  77,  78

[32]  J.   ev ík  和  D.  Aspinall。
关于  Java  内存模型中程序转换的有效性。
在ECOOP  中,  2008.  
DOI: 10.1007/978‑3‑540‑70592‑5_3。  85

[33]  RL  站点,
Ed。  Alpha  架构参考手册。
数码出版社,  1992.  58,  81

[34]  DL  Weaver  和  T.  Germond,
Eds。  SPARC  体系结构手册(第  9  版)。  PTR  Prentice  Hall,  
1994.  58,  81,  82
Machine Translated by Google
Machine Translated by Google

91

第  6  章

一致性协议
在本章中,我们回到第2  章中介绍的缓存一致性的主题。
我们在第  2  章中定义了一致性, 以了解
一致性在支持一致性方面的作用, 但我们没有深入研究具体的一致性协议是如何工作的或如何
实现的。
它们被实施。 在我们在接下来的两章中继续讨论特定类别的协议之前, 本章一般讨论一
致性协议。我们从第6.1节开始,展示了一致性协议如何工作的大图, 然后在第6.2  节中展示了如
何指定协议。

我们在第6.3节中展示了一个简单、
具体的一致性协议示例,
并在第  6.4  节中探讨了协议设计空
间。

6.1  大局
一致性协议的目标是通过强制执行在2.3节中介绍并在此重申的不变量来保持一致性。

1.单写多读(SWMR)不变。
对于任何内存位置  A,
在任何给定(逻辑)时间,
只存在一个可以
写入  A(也可以读取它)
的内核或一些只能读取  A  的内核。

2.数据值不变。
一个纪元开始时的内存位置值与其最后一个读写纪元结束时的内存位置值相
同。

为了实现这些不变量,
我们将每个存储结构(每个缓存和  LLC/内存)与一个称为一致性
控制器的有限状态机相关联。这些相干控制器的集合构成了一个分布式系统, 其中控制器相互交
换消息以确保对于每个块,始终保持  SWMR  和数据值不变量。
这些有限状态机之间的交互由一
致性协议指定。

一致性控制器有几个职责。缓存中的一致性控制器,我们称之为缓存控制器,如图  6.1  所
示。
缓存控制器必须为来自两个来源的请求提供服务。 在“核心端”,缓存控制器与处理器核心
接口。控制器接受来自内核的加载和存储,并将加载值返回给内核。高速缓存未命中会导致控制
器通过为包含内核访问的位置的块发出一致性请求(例如, 请求只读权限)来启动一致性事务。
此一致性请求通过互连网络发送到一个
Machine Translated by Google

92  6.一致性协议

或更多相干控制器。
事务由请求和为满足请求而交换的其他消息(例如,从另一个一致性控制
器发送到请求者的数据响应消息)
组成。事务的类型和作为每个事务的一部分发送的消息取决
于特定的一致性协议。

负载和
专卖店 已加载
核 价值观

缓存
缓存
控制器
网络

发布 已收到
一致性 一致性
请求和 请求和
回应 回应

互联网络

图  6.1:
缓存控制器。

在缓存控制器的“网络端”,
缓存控制器通过互连网络连接到系统的其余部分。控制器
接收它必须处理的一致性请求和一致性响应。
与核心端一样,传入一致性消息的处理取决于特
定的一致性协议。

LLC/内存中的一致性控制器,我们称之为内存控制器, 如图  6.2  所示。内存控制器类似于
缓存控制器, 不同之处在于它通常只有网络端。 因此,它不会发出一致性请求(代表加载或存
储)
或接收一致性响应。 其他代理,例如  I/O  设备,
可能会像缓存控制器、 内存控制器或两者一
样,
具体取决于它们的特定要求。

每个一致性控制器实现一组有限状态机 逻辑上每个块一个独立但相同的有限状态
机 并根据块的状态接收和处理事件(例如, 传入的一致性消息)。
对于类型  E  的事件(例
如,
从核心到缓存控制器的存储请求)到块  B,
一致性控制器采取
Machine Translated by Google

6.2.指定一致性协议  93

记忆
有限责任公司/内存
控制器
网络

发布 已收到
一致性 一致性
回应 要求

互联网络

图  6.2:
内存控制器。

作为  E  和  B  状态(例如,
只读)
的函数的动作(例如,
发出读写权限的一致性请求)。
采取这些行
动后, 控制器可能会改变  B  的状态。

6.2  指定一致性协议

我们通过指定一致性控制器来指定一致性协议。 我们可以通过多种方式指定相干控制器, 但相干


控制器的特定行为适合表格规范[9]。 如表6.1所示,
我们可以将控制器指定为一个表, 其中行对应
块状态, 列对应事件。
我们将表中的状态/事件条目称为转换, 与块  B  相关的事件  E  的转换包括  
(a)  E  发生时采取的操作和  (b)  块  B  的下一个状态。 我们表示转换以“action/next  state”
的格
式, 如果下一个状态是当前状态, 我们可以省略“next  state”部分。 作为表6.1中的转换示例, 如果
从内核接收到块  B  的存储请求并且块  B  处于只读状态  (RO), 则该表显示控制器的转换将执行操
作“发出读写权限的一致性请求(给块B)” 并将块B的状态更改为RW。

为简单起见,表6.1中的示例故意不完整,但它说明了表格规范方法捕获一致性控制器行为
的能力。要指定一个一致性协议, 我们只需要完全指定缓存控制器和内存控制器的表。

一致性协议之间的差异在于控制器规格的差异。 这些差异包括不同的区块状态、
交易、事件
和转换集。在第6.4  节中,
我们通过探索每个方面的选项来描述一致性协议设计空间,但我们首先
指定一个简单、具体的协议。
Machine Translated by Google

94  6.一致性协议

表  6.1:
表格规范方法。
这是缓存一致性控制器的不完整规范。
表中的每个条目指定所采取的操作和
块的下一个状态。

事件

传入一致性请求以获取读写
加载请求来自 存储请求来自
状态的块
核 核

问题连贯性重新 问题连贯性重新
不可读或

状 请求只读权限/RO 请求读写权限/RW <无动作>
可写  (N)

问题连贯性重新
从缓存中提供数据
只读  (RO) 请求读写权限/RW <无动作>/N
核心

从缓存中提供数据 发送块到
读写  (RW) 将数据写入缓存
核心 请求者/N

6.3  简单一致性协议示例
为了帮助理解一致性协议, 我们现在提出一个简单的协议。
我们的系统模型是第  2.1  节中的基线系
统模型, 但互连网络仅限于共享总线:一组共享线路,
内核可以在其上发布消息并让所有内核和  
LLC/内存观察到它。

每个缓存块可以处于两种稳定的一致性状态之一: I(无效) 和  V(有效)。


LLC/内存中的每个块也可以处于两种一致性状态之一: I  和  V。
在  LLC/内存中,状态  I  表示所有缓存
都保存状态  I  的块,
状态  V  表示一个缓存保存状态  V  中的块。 缓存块还有一个瞬态,  IVD, 将在下
面讨论。 在系统启动时, 所有缓存块和  LLC/内存块都处于状态  I。 每个内核都可以向其缓存控制器发
出加载和存储请求; 当缓存控制器需要为另一个块腾出空间时, 它会隐式地生成一个  Evict  Block  
事件。 缓存中未命中的加载和存储启动一致性事务, 如下所述, 以获得缓存块的有效副本。 与本入门中
的所有协议一样, 我们假设有一个写回缓存; 也就是说, 当存储命中时, 它只将存储值写入(本地) 缓
存,并等待将整个块写回  LLC/内存以响应  Evict  Block  事件。

使用三种类型的总线消息实现了两种类型的一致性事务:  Get请求一个块,  DataResp传
输块的数据,  Put将块写回内存控制器。 在加载或存储未命中时,
缓存控制器通过发送  Get  消息并
等待相应的  DataResp  消息来启动  Get  事务。
获取
Machine Translated by Google

6.3.一个简单的一致性协议的例子  95

事务是原子的, 因为在缓存发送  Get  和该  Get  的  DataResp  出现在总线上之间,
没有其他事务(Get  
或  Put)
可以使用总线。
在  Evict  Block  事件中,
缓存控制器将包含整个缓存块的  Put  消息发送到内
存控制器。

我们在图  6.3  中说明了稳定相干态之间的转换。 我们使用前言“Own” 和“Other”


来区分由给
定缓存控制器发起的事务消息和由其他缓存控制器发起的事务消息。 请注意, 如果给定的缓存控制
器具有处于状态  V  的块, 并且另一个缓存使用  Get  消息(表示为  Other  Get)
请求它,则拥有的缓存
必须以块响应(使用  DataResp  消息,
未显示)并转换到状态  I .

自己购买或其他购买 自己获取+DataResp

图  6.3:
缓存控制器块稳定状态之间的转换。

表6.2和6.3更详细地指定了协议。表中的阴影条目表示不可能的转换。 例如,
缓存控制器不应该
在总线上看到它自己的  Put  请求,
请求缓存中处于状态  V  的块(因为它应该已经转换到状态  I)。

瞬态  IVD对应于状态  I  中的一个块,
它在转换到状态  V  之前正在等待数据(通过  DataResp  
消息)。当稳定状态之间的转换不是原子的时, 会出现瞬态。 在这个简单的协议中, 单个消息的发送和
接收是原子的, 但是从内存控制器获取一个块需要发送一个  Get  消息并接收一个  DataResp  消息,
两者之间有一个不确定的间隔。  IVD状态表示协议正在等待  DataResp。 我们在第  6.4.1  节中更深
入地讨论瞬态。

该一致性协议在许多方面都过于简单且效率低下,
但提出该协议的目的是为了了解协议是如
何指定的。在介绍不同类型的一致性协议时,
我们在整本书中都使用这种规范方法。
Machine Translated by Google

96  6.一致性协议

表  6.2:
缓存控制器规范。
阴影条目是不可能的,
空白条目表示被忽略的事件。

巴士活动

核心事件
其他核心的消息
自己交易的消息

状 交易

其他数据响应
加载或 驱逐 自己的 数据响应 自己的 其他 其他
店铺 堵塞 得到 自取 放 得到 放
得到

我 问题获取/
体外诊断

体外诊断
将数据复制到缓存
停止加载或存 中,
执行
失速驱逐
储 加载或存储
/在

V执行 发行看跌期 发送
加载或 权(含数据) 数据响应
店铺 /我 /我

表  6.3:
内存控制器规格

巴士活动
状态
得到 放

将DataResp消息中的数据块发送给
requestor/V
在 更新内存中的数据块/I

6.4  一致性协议设计概述
空间

如第  6.1  节所述,
一致性协议的设计者必须为系统中的每种类型的一致性控制器选择状
态、
事务、 事件和转换。稳定状态的选择在很大程度上独立于一致性协议的其余部分。 例如,
有两种不同类别的一致性协议, 称为侦听和目录, 架构师可以设计具有相同稳定状态集的
侦听协议或目录协议。 我们在第  6.4.1  节中讨论独立于协议的稳定状态。
同样,交易的选择
也很大程度上独立于具体的协议, 我们将在6.4.2  节中讨论交易。

然而,
与稳定状态和事务的选择不同,
事件、
转换和特定的瞬态高度依赖于一致性协议,

法讨论
Machine Translated by Google

6.4.一致性协议设计空间概述  97

隔离中。
因此,
在第  6.4.3  节中,
我们讨论了一致性协议中的一些主要设计决策。

6.4.1  状态
在只有一个参与者的系统中(例如, 没有相干  DMA  的单核处理器), 缓存块的状态要么有效要么
无效。
如果需要区分脏块,缓存块可能有两种可能的有效状态。 脏块具有比该块的其他副本最近写
入的值。例如,
在具有回写式  L1  缓存的二级缓存层次结构中, L1  中的块相对于  L2  缓存中的陈旧
副本可能是脏的。

具有多个参与者的系统也可以只使用这两种或三种状态, 如第6.3  节中所述,
但我们通常希
望区分不同类型的有效状态。 我们希望在其状态中编码缓存块的四个特征: 有效性、
脏性、排他性和
所有权[10]。
后两个特征是具有多个参与者的系统所独有的。

‧  有效性:
有效块具有该块的最新值。
该块可以被读取,
但只有当它也是独占时才可以被写入。

‧脏:
在单核处理器中,如果缓存块的值是最新的值, 则该缓存块是脏的, 该值与LLC /内存中
的值不同,并且缓存控制器负责最终更新LLC /memory  这个新值。
清洁一词通常用作脏的
反义词。

‧  独占性:
如果缓存块是系统中该块的唯一私有缓存副本, 则该缓存块是独占的(即,
该块可
能不会缓存在除共享  LLC  之外的任何其他地方)。

‧  所有权:
如果高速缓存控制器(或内存控制器)负责响应该块的一致性请求,
那么它就是该
块的所有者。 在大多数协议中,给定块始终只有一个所有者。
拥有的块可能不会从缓存中逐
出以为另一个块腾出空间(由于容量或冲突未命中), 而不会将块的所有权交给另一个一
致性控制器。 在某些协议中,
非拥有的块可能会被静默地驱逐(即,不发送任何消息)。

在本节中,
我们首先讨论一些常用的稳定状态 当前不在一致性交易中的区块状态 然后
讨论使用瞬态来描述当前在交易中的区块。

1  这里的术语可能会造成混淆,
因为有一种缓存一致性状态称为“独占”,
但还有其他缓存一致性状
态在此处定义的意义上是独占的。
Machine Translated by Google

98  6.  一致性协议
稳定状态许
多一致性协议使用经典的五状态  MOESI  模型的一个子集, 该模型首先由  Sweazey  和  Smith  [10]  
引入。
这些  MOESI(通常发音为“MO‑sey” 或“mo‑EE‑see”)
状态指的是缓存中块的状态, 最基本
的三种状态是  MSI;可以使用  O  和  E  状态,
但它们不是基本状态。 这些状态中的每一个都具有前面描
述的特征的不同组合。

‧  M(odified):
该块是有效的、独占的、
拥有的并且可能是脏的。 该块可以被读取或写入。
缓存只
有该块的有效副本, 缓存必须响应对该块的请求,
并且  LLC/内存中的块副本可能已过时。

‧  S(hared):
该块有效但不排他、
不脏且不被拥有。
缓存有块的只读副本。
其他缓存可能具有该
块的有效、 只读副本。

‧  I(nvalid):
该块无效。
缓存要么不包含该块,要么包含它可能无法读取或写入的可能过时的副
本。 在本入门中, 我们不区分这两种情况,
尽管有时前一种情况可能表示为“不存在”状态。

最基本的协议仅使用  MSI  状态,
但有理由添加  O  和  E  状态以优化某些情况。 我们将在后面的
章节讨论带有和不带有这些状态的侦听和目录协议时讨论这些优化。 现在,这里是  MOESI  状态的完
整列表:

‧  修改的)

‧  O(wned):
该块是有效的、
拥有的并且可能是脏的,
但不是独占的。
缓存有块的只读副本, 并且
必须响应对该块的请求。 其他缓存可能有该块的只读副本,
但它们不是所有者。  LLC/内存中的
块副本可能已过时。

‧  E(xclusive):
该块有效、
排他且干净。
缓存有块的只读副本。其他缓存都没有该块的有效副本,
并且  LLC/内存中的块副本是最新的。 在本入门手册中,我们认为块处于排他状态时被拥有, 尽
管有些协议不将排他状态视为所有权状态。 当我们在后面的章节中介绍  MESI  侦听和目录协议
时, 我们会讨论与使独占块所有者或

不是。

‧  S(共享)

‧  无效的)
Machine Translated by Google

6.4.一致性协议设计空间概述  99

我们在图  6.4  中说明了  MOESI  状态的维恩图。维恩图显示了哪些状态共享哪些特征。 除  I  
之外的所有状态均有效。  M、 O  和  E  是所有权状态。  M  和  E  都表示排他性,
因为没有其他缓存
具有该块的有效副本。
M  和  O  都表示该块可能是脏的。 回到第6.3  节中的简单示例, 我们观察到该协议有效地将  MOES  
状态压缩到  V
状态。

所有权(注意:
独占状态并不总是被视
为所有权状态)
拥有

修改的 有效性

肮脏

独家的
排他性
无效的
共享

图  6.4:  MOESI  状态。

MOESI  状态虽然很常见, 但并不是一组详尽的稳定状态。
例如,
F(orward)  状态类似于  O  状态,
只是它是干净的(即,
LLC/内存中的副本是最新的)。有许
多可能的相干状态, 但我们在本入门教程中将注意力集中在众所周知的  MOESI  状态上。

瞬态  到目前为
止, 我们只讨论了当块没有当前一致性活动时发生的稳定状态, 并且只有这些稳定状态在引用协
议时使用(例如, “具有  MESI  的系统协议”)。
然而, 正如我们在  6.3  节的示例中看到的那样,
在从一个稳定状态转换到另一个稳定状态期间可能存在瞬态。 在  6.3  节中,
我们有瞬态  IVD  (在  
I  中,转到  V, 等待  DataResp)。在更复杂的协议中,我们可能会遇到几十种瞬态。 我们使用符号
XYZ  对这些状态进行编码, 表示该块正在从稳定状态  X  过渡到稳定状态  Y, 并且在发生类型  Z  的
事件之前不会完成过渡。 例如, 在后面章节的协议中,我们使用  IMD来表示一个块以前在  I  中, 一
旦  D(ata)  消息到达该块, 它将变为  M。
Machine Translated by Google

100  6.  一致性协议

LLC/内存中块的状态到目前为止我们
讨论的状态 稳定的和瞬态的 都与驻留在缓存中的块有关。  LLC  和内存中的块也有与其关
联的状态, 并且有两种通用方法来命名  LLC  和内存中块的状态。
命名约定的选择不会影响功能
或性能; 这只是一个规范问题,可能会使不熟悉该公约的架构师感到困惑。

‧  以缓存为中心:在我们认为最常见的这种方法中, LLC  和内存中块的状态是缓存中该块状
态的集合。
例如, 如果一个块在  I  中的所有缓存中,则该块的  LLC/内存状态为  I。
如果一个块在  S  中的
一个或多个缓存中, 则  LLC/内存状态为  S。
如果一个块是在  M  中的单个缓存中,则  LLC/内
存状态为  M。

‧  以内存为中心: 在这种方法中,LLC/内存中块的状态对应于内存控制器对该块的权限(而
不是缓存的权限)。 例如,
如果一个块在  I  中的所有缓存中, 那么这个块的  LLC/内存状态是  
O(不是  I, 在以缓存为中心的方法中), 因为  LLC/内存的行为就像块的所有者。 如果一个
块在  S  中的一个或多个缓存中, 则出于同样的原因, LLC/内存状态也是  O。
但是, 如果该块位
于  M  或  O  中的单个缓存中,则  LLC/内存状态为  I, 因为  LLC/内存具有该块的无效副本。

本入门中的所有协议都使用以缓存为中心的名称来表示  LLC  和内存中的块状态。

维护块状态系统实现必
须维护与缓存、 LLC  和内存中的块相关联的状态。对于高速缓存和  LLC, 这通常需要将每块高速缓
存状态最多扩展几位, 因为稳定状态的数量通常很小(例如, MOESI  协议的  5  个状态需要每块  
3  位)。一致性协议可能有更多的瞬时状态, 但只需要为那些具有未决一致性事务的块维护这些
状态。 实现通常通过向未命中状态处理寄存器  (MSHR)  或用于跟踪这些待处理事务的类似结构
添加额外位来维护这些瞬态[4]。

对于内存而言, 更大的总容量似乎会构成重大挑战。 然而,许多当前的多核系统都维护一个


包容性  LLC, 这意味着  LLC  维护着缓存在系统中任何位置的每个块(甚至是“独占” 块)
的副本。
使用包容性  LLC, 内存不需要明确表示一致性状态。 如果一个块驻留在  LLC  中,
则它在内存中的状
态与它在  LLC  中的状态相同。 如果该块不在  LLC  中,则其在内存中的状态隐式无效, 因为不存在
包含性  LLC  意味着该块不在任何缓存中。 侧边栏
Machine Translated by Google

6.4.一致性协议设计空间概述  101

讨论了在包含  LLC  的多核出现之前的日子里如何维护内存状态。
上面关于内存的讨论假设一个系统只有一个多核芯片, 这本书的大部分内容也是如此。
具有多个多核
芯片的系统可能会受益于内存中逻辑上的显式一致性状态。

6.4.2  交易

大多数协议都有一组相似的事务,因为一致性控制器的基本目标是相似的。 例如,几乎所有协议都有
一个用于获取对块的共享(只读)访问权限的事务。 在表6.4中,
我们列出了一组常见的事务,并且对于
每个事务,我们都描述了发起事务的请求者的目标。 这些事务都是由高速缓存控制器发起的, 这些高速
缓存控制器响应来自其相关核心的请求。在表6.5  中,我们列出了内核可以对其缓存控制器发出的请
求,
以及这些内核请求如何引导缓存控制器启动一致性事务。

边栏:多核之前:
在内存中保持一致性状态传统的多核之前的协议需要为每个
内存块保持一致性状态,
并且它们不能使用  LLC,
如第6.4.1  节中所述。
我们简要讨论了几
种维持这种状态的方法以及相关的工程权衡。

用状态位扩充每个内存块。 最通用的实现是向每个内存块添加额外的位以保持一致性状
态。 如果内存中有  N  个可能的状态, 则每个块需要  log2N个额外位。 虽然这种设计是完全通用的
并且在概念上很简单, 但它有几个缺点。 首先, 额外的位可能会以两种方式增加成本。 对于现代的
面向块的  DRAM  芯片来说, 添加两个或三个额外的位是很困难的, 这些芯片通常至少有  4  位宽,
而且通常更宽。 此外, 内存中的任何更改都会排除使用商品  DRAM  模块(例如  DIMM), 这会显
着增加成本。 幸运的是, 对于每个块只需要几位状态的协议, 可以使用修改后的  ECC  代码来存储
这些状态。 通过将  ECC  保持在更大的粒度(例如, 512  位而不是  64  位), 可以释放足够的代码
空间来“隐藏” 一些额外的位, 同时使用商品  DRAM  模块  [ 1、
5、7 ] 。
第二个缺点是,将状态位存储
在  DRAM  中意味着获取状态会导致整个  DRAM  延迟, 即使在块的最新版本存储在其他缓存中的
情况下也是如此。 在某些情况下, 这可能会增加缓存到缓存一致性传输的延迟。 最后,将状态存储
在  DRAM  中意味着所有状态更改都需要一个  DRAM  读取‑修改‑写入周期, 这可能会影响功率和  
DRAM  带宽。

在内存中为每个块添加单个状态位。  Synapse  [3]使用的一个设计选项是使用与
Machine Translated by Google

102  6.  一致性协议

每个内存块。
很少有块处于瞬态状态,
并且可以使用小型专用结构来维护这些状态。
此设计是更完整的第一个
设计的子集,
存储成本最低。

零位逻辑或。
为了避免必须修改内存,
我们可以让缓存按需重建内存状态。
块的内存状态是块在每个缓
存中的状态的函数,
因此,
如果所有缓存聚合它们的状态,
它们就可以确定内存状态。
系统可以通过让所有内核
向逻辑或门(或或门树)
发送一个“IsOwned”
信号来推断内存是否是块的所有者,
输入数量等于高速缓存的
数量.如果此  OR  的输出为高,
则表示缓存是所有者;
如果输出低,
则内存是所有者。
该解决方案避免了在内存中
维护任何状态的需要。
然而,
使用逻辑门或线或实现快速或可能很困难。

a不要将此  IsOwned  信号与  Owned  缓存状态混淆。  IsOwned  信号由处于所有权状态的高速缓存断
言,
该状态包括拥有、 修改和独占高速缓存状态。

表  6.4:
常见交易

交易 请求者的目标
获取共享  (GetS) 获取处于共享(只读)
状态的块
GetModified  (GetM)  获取修改后(只读)
状态的块
升级  (Upg) 将块状态从只读(共享或拥有)
升级为读写
(修改的);  Upg(与  GetM  不同)
不需要将数据发送到  re
询问者
普特共享  (PutS) 共享状态中的逐出块a

PutExclusive  (PutE)  逐出排他状态的块a  PutOwned  (PutO)
在  Owned  状态下驱逐区块

PutModified  (PutM)  Evict  block  in  Modified  state  a一些协议
不需要一致性事务来驱逐共享块和/或独占块(即,
PutS  和/或  PutE  是“静默的”)。

尽管大多数协议使用一组相似的事务,
但它们在一致性控制器如何交互以执行事务方面有很大差异。
正如我
们将在下一节中看到的,
在某些协议(例如,
监听协议)
中,缓存控制器通过向系统中的所有一致性控制器广播  GetS  
请求来启动  GetS  事务,
并且无论哪个控制器当前是块的所有者都会响应请求者发送包含所需数据的消息。
相反,

其他协议(例如,
目录协议)
中,缓存控制器启动
Machine Translated by Google

6.4.一致性协议设计空间概述  103

表  6.5:
对缓存控制器的常见核心请求

事件 (典型)
缓存控制器的响应
加载 如果缓存命中,
则使用缓存中的数据进行响应;
否则发起  GetS  交易
如果缓存命中状态  E  或  M,
则将数据写入缓存;
否则启动  GetM  或
店铺
升级交易
原子读 如果缓存命中状态  E  或  M,
自动执行  RMW  语义;
别的
修改写入 GetM  或  Upg  交易
如果缓存命中(在  I‑cache  中),
则使用来自缓存的指令进行响应;
否则启动
取指令
获取交易

只读预取
如果缓存命中,
则忽略;  else  可以选择性地启动  GetS  事务a

读写 如果在状态  M  缓存命中,
则忽略;  else  可以选择启动  GetM  或  Upg
prefetch   交易 A

Replacement   根据块的状态,
启动  PutS、
PutE、
PutO  或  PutM  交易
a高速缓存控制器可以选择忽略来自内核的预取请求。

通过向特定的、
预定义的一致性控制器发送单播  GetS  消息来执行  GetS  事务,
该控制器可以
直接响应或可以将请求转发给将响应请求者的另一个一致性控制器。

6.4.3  主要协议设计选项设计一致性协议有许多不同的方
法。
即使对于同一组状态和交易,也有许多不同的可能协议。
协议的设计决定了每个一致性控
制器可能发生的事件和转换;
与状态和交易不同,无法提供独立于协议的可能事件或转换的列
表。

尽管一致性协议有巨大的设计空间,但有两个主要设计
对协议其余部分有重大影响的决定,我们接下来将讨论它们。

侦听与目录一致性协议
主要分为两类: 侦听和目录。 我们现在对这些协议进行简要概述,
并将对它们的深入介绍分别
推迟到第7  章和第  8  章。

‧  侦听协议:缓存控制器通过向所有其他一致性控制器广播请求消息来发起对块的请
求。 相干控制器统称为
Machine Translated by Google

104  6.  一致性协议

“做正确的事”,
例如,发送数据以响应另一个核心的请求(如果它们是所有者)。侦听
协议依靠互连网络以一致的顺序向所有核心传送广播消息。大多数侦听协议假定请求按
总顺序到达,
例如,通过共享线路总线,
但更高级的互连网络和宽松的顺序是可能的。

‧  目录协议:
高速缓存控制器通过将块单播到作为该块所在位置的内存控制器来发起对块
的请求。 内存控制器维护一个目录, 该目录保存有关  LLC/内存中每个块的状态,例如当前
所有者的身份或当前共享者的身份。 当对块的请求到达主目录时, 内存控制器查找该块的
目录状态。 例如,
如果请求是  GetS,
内存控制器将查找目录状态以确定所有者。 如果  LLC/
内存是所有者, 则内存控制器通过向请求者发送数据响应来完成事务。 如果缓存控制器是
所有者, 则内存控制器将请求转发给所有者缓存; 当所有者缓存收到转发的请求时, 它通过
向请求者发送数据响应来完成事务。

监听与目录的选择涉及权衡。侦听协议在逻辑上很简单,但它们无法扩展到大量内核,因
为广播无法扩展。目录协议是可扩展的,因为它们是单播的,但许多事务需要更多时间,
因为当家
庭不是所有者时,它们需要发送额外的消息。此外,协议的选择会影响互连网络(例如,经典的
侦听协议需要请求消息的总顺序)。

无效与更新一致性协
议中的另一个主要设计决策是决定当核心写入块时要做什么。
此决定与协议是侦听还是目录无
关。
有两种选择。
‧  使协议无效:
当核心希望写入块时,
它会启动一致性事务操作以使所有其他缓存中的副
本无效。 一旦副本失效,
请求者就可以写入块,而另一个核心不可能读取块的旧值。
如果另
一个核心希望在其副本失效后读取该块, 则它必须启动一个新的一致性事务来获取该块,
并且它将从写入它的核心获得副本,从而保持一致性。

‧  更新协议:
当一个内核希望写入一个块时,
它会启动一个一致性事务来更新所有其他缓
存中的副本, 以反映它写入块的新值。
再一次,
做出这个决定需要权衡取舍。 更新协议减少了内核读取新写入块的延迟,
因为内
核不需要启动并等待  GetS  事务完成。
然而,
更新协议通常消耗
Machine Translated by Google

6.5.参考文献  105

带宽比无效协议大得多,因为更新消息比无效消息大(一个地址和一个新值, 而不仅仅是一个地
址)。
此外,更新协议极大地复杂化了许多内存一致性模型的实现。 例如,
当多个缓存必须对块的多个
副本应用多个更新时,保持写入原子性(第5.5  节)
变得更加困难。
由于更新协议的复杂性,它们很少
被实现;在本入门教程中,
我们重点关注更为常见的无效协议。

混合设计对于两
个主要的设计决策,
一个选择是开发混合设计。 有些协议结合了监听和目录协议[2、
6 ]  的各个方面,
有些协议结合了无效和更新协议[8]  的各个方面。
设计空间丰富,
建筑师不局限于遵循任何特定的设
计风格。

6.5  参考文献
[1]  A.查尔斯沃思。  Sun  6800  中的  Sun  Fireplane  SMP  互连。
在Proc。
第九届热互连研讨会,  
2001  年  8  月。
DOI:
10.1109/his.2001.946691。  101

[2]  P.  康威和  B.  休斯。  AMD  Opteron  北桥架构。  IEEE微,  27(2):10–21,
2007  年  3  月/4  月。
DOI: 10.1109/mm.2007.43。  105

[3]  SJ  弗兰克。 紧密耦合的多处理器系统加快了内存访问时间。
电子学,  57(1):164–169,
1984  
年  1  月。
101

[4]  D.克罗夫特。 无锁定指令获取/预取缓存组织。 在过程中。


第八届计算机体系结构年度研讨会,  
1981  年  5  月。
DOI:
10.1145/285930.285979。
100

[5]  HQ  Le  等。  IBM  POWER6  微架构。  IBM  研究与开发杂志
在撒谎,  51(6),  2007.  DOI:  10.1147/rd.516.0639。  101

[6]  MMK  Martin、
DJ  Sorin、 MD  Hill  和  DA  Wood。
带宽自适应侦听。
在过程中。 第  8  届  IEEE  高性能计算机体系结构研讨会, 第  251‑262  页,
2002  年  1  月。
DOI:
10.1109/hpca.2002.995715。  105

[7]  A.  Nowatzyk、
G.  Aybay、
M.  Browne、
E.  Kelly  和  M.  Parkin。  S3.mp  可伸缩共享内存多处
理器。 在过程中。 并行处理国际会议, 第一卷。  I, 第  1‑10  页, 1995  年  8  月。
DOI:
10.1109/
hicss.1994.323149。  101

[8]  A.  Raynaud、
Z.  Zhang  和  J.  Torrellas。
可扩展共享内存多处理器的距离自适应更新协议。 在
过程中。 第二届  IEEE  高性能计算机体系结构研讨会,  1996  年  2  月。 DOI:
10.1109/
hpca.1996.501197。  105
Machine Translated by Google

106  6.一致性协议

[9]  DJ  Sorin、
M.  Plakal、
MD  Hill、
AE  Condon、
MM  Martin  和  DA  Wood。
指定和验证广播和多播侦听缓存一致性协议。
IEEE  并行和分布式系统交易,  13(6):556–578, 2002  年  6  月。 DOI:  10.1109/
tpds.2002.1011412。  93

[10]  P.  Sweazey  和  AJ  Smith。
一类兼容的缓存一致性协议及其由  IEEE  Futurebus  的支持。 在
过程中。 第  13  届计算机体系结构国际研讨会, 第  414‑423  页,
1986  年  6  月。
DOI:
10.1145/17356.17404。  97,  98
Machine Translated by Google

107

第7章

窥探一致性协议
在本章中,
我们介绍了窥探一致性协议。 侦听协议是第一个广泛部署的协议类别,并且它们继续在各种
系统中使用。侦听协议提供了许多有吸引力的特性,包括低延迟一致性事务和比替代目录协议(第8  章)
概念上更简单的设计。

我们首先在较高层次上介绍监听协议(第7.1  节)。 然后,
我们展示了一个简单的系统, 该系统具
有完整但不复杂的三态  (MSI)  侦听协议(第7.2  节)。
该系统和协议作为我们稍后添加系统功能和协
议优化的基线。我们讨论的协议优化包括添加独占状态(第7.3  节) 和拥有状态(第7.4  节),
以及更高
性能的互连网络(第7.5节和7.6  节)。 然后,
我们将讨论带有侦听协议的商业系统(第7.7  节), 然后在
讨论侦听及其未来(第7.8  节) 结束本章之前。

鉴于一些读者可能不希望深入研究窥探,
我们有  orga
对本章进行了调整,
以便读者可以选择浏览或跳过第7.3‑7.6节。

7.1  窥探简介
侦听协议基于一个想法:所有一致性控制器以相同的顺序观察(侦听)一致性请求,
并共同“做正确的
事”
以保持一致性。通过要求对给定块的所有请求按顺序到达,侦听系统使分布式一致性控制器能够正
确更新共同表示缓存块状态的有限状态机。

传统的侦听协议将请求广播到所有一致性控制器,
包括发起请求的控制器。一致性请求通常在有
序的广播网络(例如公共汽车)上传播。
有序广播确保每个一致性控制器以相同顺序观察相同系列的一
致性请求,即存在一致性请求的总顺序。
由于总顺序包含所有每块顺序,
因此总顺序保证所有一致性控
制器都可以正确更新缓存块的状态。

为了说明以相同的每块顺序处理一致性请求的重要性, 请考虑表7.1和7.2中的示例,其中核心  C1  
和核心  C2  都希望在状态  M  中获得相同的块  A。
在表7.1  中,
所有三个一致性控制器观察一致性请求的
相同每个块顺序, 并共同维护单写入器  ‑  多读取器(SWMR) 不变性。
块的所有权从  LLC/内存进展到核
心  C1  到
Machine Translated by Google

108  7.  窥探一致性协议

表  7.1:
窥探一致性示例。
所有活动都涉及块  A(表示为“A:”)。

时芯C1 核心C2 有限责任公司/内存


0答:
我 答:
我 答:

(有限责任公司/记忆是所有者)
A:从Core  C1/M获取M
1  A:GetM  来自  Core  C1/MA:GetM  来自  Core  C1/I
(LLC/内存不是所有者)
2  A:
从核心  C2/IA  获取  M:
从核心  C2/MA  获取  M:
从核心  C2/M  获取  M

表  7.2:
窥探(In)
一致性示例。
所有活动都涉及块  A(表示为“A:”)。

时芯C1 核心C2 有限责任公司/内存


0答:
我 答:
我 答:

(有限责任公司/记忆是所有者)
A:从Core  C1/M获取M
1  A:
从核心  C1/MA  获取  M:
从核心  C2/M  获取  M
(LLC/内存不是所有者)
2  A:
从核心  C2/IA  获取  M:
从核心  C1/IA  获取  M:
从核心  C2/M  获取  M

核心C2。作为每个观察到的请求的结果, 每个一致性控制器独立地得出关于块状态的正确结论。
相反,表7.2说明了如果核心  C2  观察到与核心  C1  和  LLC/内存不同的每块请求顺序, 可能会出
现不一致。 首先,我们遇到核心  C1  和核心  C2  同时处于状态  M  的情况,这违反了  SWMR  不变
量。
接下来, 我们有一种情况, 没有一致性控制器认为它是所有者, 因此此时的一致性请求不会收
到响应(可能导致死锁)。

传统的侦听协议会在所有块中创建一致性请求的总顺序, 即使一致性只需要每个块的请
求顺序。 拥有全序可以更容易地实现需要全序内存引用的内存一致性模型, 例如  SC  和  TSO。

虑表7.3中涉及两个块  A  和  B  的示例;
每个块只被请求一次,
因此系统会简单地观察每个块的请
求顺序。

然而,
由于核心  C1  和  C2  无序地观察到  GetM  和  GetS  请求,
此执行违反了  SC  和  TSO  内存一
致性模型。
Machine Translated by Google

7.1.窥探简介  109

表  7.3:
每个块的顺序、 连贯性和一致性。 与地址  A  相关的状态和操作前面有前缀“A:”, 我们将
状态  X  中值为  V  的块  A  表示为“A:X[V]”。 如果该值是陈旧的, 我们将其省略(例如, “A:I”)。
请注意, 此示例中有两个块, A  和  B,
A  最初在核心  C2  中处于状态  S,
而  B  最初在核心  C1  中处
于状态  M。

时芯C1 核心C2 有限责任公司/内存


答:
我 答:
S[0] 答:
S[0]
0
B:M[0] 双 乙:

A:从核心  C1/M[0]  获取  M A:S[0]   答:
S[0]
1个 存储  A  =  1 双 乙:

B:M[0]
A:M[1]  存 答:
S[0] A:从Core  C1/M获取M
2个 储  B  =  1 双 乙:

B:M[1]
答:
M[1] 答:
S[0] 是
3个

B:从核心  C2/S  获取  S[1] 双 B:从核心  C2/S  获取  S[1]
答:
M[1] 答:
S[0] 是
4个
B:S[1] B:从核心  C2/S  获取  S[1] B:S[1]  
r1  =  B[1]
答:
M[1] A:S[0]   是
5个
B:S[1] r2  =  A[0] B:S[1]
B:S[1]
答:
M[1] A:从  Core1/I  获取  M 是
6个

B:S[1] B:S[1]   B:S[1]


r1  =  1,  r2  =  0  违反  SC  和  TSO

侧边栏: 侦听如何依赖于一致性请求的总顺序乍一看, 读者可能会假设表7.3


中的问题出现是因为在周期  1  中块  A  违反了  SWMR  不变量, 因为  C1  有一个  M  副本,
而  
C2  仍然有S副本。 然而, 表7.4说明了相同的示例, 但强制执行一致性请求的总顺序。 此示例
在第  4  周期之前是相同的, 因此具有相同的明显  SWMR  违规。然而, 就像众所周知的“森林
中的树木” 一样,这种违规行为不会造成问题, 因为它没有被观察到(即, “那里没有人听
到”)。 具体来说, 因为内核以相同的顺序看到两个请求, 所以  C2  在看到块  B  的新值之前
使块  A  无效。因此, 当  C2  读取块  A  时,
它必须获得新值并因此产生正确的  SC  和TSO  执
行。
Machine Translated by Google

110  7.  窥探一致性协议

传统的侦听协议使用一致性请求的总顺序来确定在基于侦听顺序的逻辑时间内何
时观察到特定请求。 在表7.4的示例中, 由于总顺序,核心  C1  可以推断  C2  将在看到  B  的  
GetS  之前看到  A  的  GetM,
因此  C2  在收到一致性消息时不需要发送特定的确认消息。
这种对请求接收的隐式确认将侦听协议与我们在下一章中研究的目录协议区分开来。

表  7.4:
总体秩序、 连贯性和一致性。 属于地址  A  的状态和操作前面有前缀“A:”,
我们将状态  
X  中的块  A  和值  V  表示为“A:X[V]”。
如果该值是陈旧的,
我们将其省略(例如,
“A:I”)。

时芯C1 核心C2 有限责任公司/内存


答:
我 答:
S[0] 答:
S[0]
0
B:M[0] 双 乙:

A:从核心  C1/M[0]  获取  M A:S[0]   答:
S[0]
1个 存储  A  =  1 双 乙:

B:M[0]
A:M[1]  存 答:
S[0] A:从Core  C1/M获取M
2个 储  B  =  1 双 乙:

B:M[1]
答:
M[1] 答:
S[0] 是
3个

B:从核心  C2/S  获取  S[1] 双 B:从核心  C2/S  获取  S[1]
答:
M[1] A:从  Core1/I  获取  M 是
4个

B:S[1] 双 B:S[1]
答:
M[1] 答:
我 是
5个
B:S[1] B:从核心  C2/S  获取  S[1] B:S[1]  
r1  =  B[1]
A:
从核心  C2/S  获取  S[1] A:
从核心  C2/S[1]  获取  S  r2  =  A[1] A:
从核心  C2/S  获取  S[1]
6个
B:S[1] B:S[1]
B:S[1]  
r1  =  1,  r2  =  1  满足  SC  和  TSO

我们在边栏中讨论了一些关于需要整体排序的更微妙的问题。
要求以总顺序观察广播一致性请求对用于实现传统侦听协议的互连网络具有重要意义。

因为许多一致性控制器可能同时尝试发出一致性请求,
Machine Translated by Google

7.2.基线监听协议  111

互连网络必须将这些请求序列化为某种总顺序。 然而网络决定了这个顺序,
这个机制被称
为协议的序列化(排序) 点。在一般情况下, 一致性控制器发出一致性请求,
在序列化点请
求的网络命令并将其广播到所有控制器, 发出控制器通过监听它从中接收的请求流来了
解它的请求在哪里被订购控制器。 作为一个具体而简单的例子, 考虑一个使用总线来广播
一致性请求的系统。  Coherence  控制器必须使用仲裁逻辑来确保总线上一次只发出一
个请求。该仲裁逻辑充当串行化点, 因为它有效地确定请求出现在总线上的顺序。 一个微
妙但重要的一点是,一致性请求是在仲裁逻辑将其序列化时立即排序的, 但控制器可能只
能通过监听总线来确定此顺序, 以观察在其自己的请求之前和之后出现的其他请求。 因
此,
一致性控制器可以在序列化点确定它之后的几个周期观察总请求顺序。

到目前为止,我们只讨论了一致性请求,但没有讨论对这些请求的响应。 这种看似疏
忽的原因是侦听协议的关键方面围绕着请求。 响应消息几乎没有限制。它们可以在不需
要支持广播也没有任何排序要求的独立互连网络上传输。 由于响应消息携带数据,
因此比
请求长得多,因此能够在更简单、成本更低的网络上发送它们有很大的好处。 不表,
响应消
息不影响一致性事务的序列化。从逻辑上讲,一致性事务(由广播请求和单播响应组成)
在请求被订购时发生,而不管响应何时到达请求者。 请求出现在总线上和响应到达请求者
之间的时间间隔确实会影响协议的实现(例如, 在这个间隔期间,是否允许其他控制器请
求这个块?如果是,那么如何请求者响应?),但不影响事务的序列化。 1

7.2  基线侦听协议
在本节中,
我们提出了一个简单的、 未优化的侦听协议, 并描述了它在两种不同系统模型
上的实现。
第一个简单的系统模型说明了实现侦听一致性协议的基本方法。 第二个稍微复
杂的基线系统模型说明了即使是相对简单的性能改进也可能如何影响一致性协议的复杂
性。
这些示例提供了对侦听协议的关键特性的深入了解, 同时揭示了激发本章后续部分中
介绍的特性和优化的低效率。第  7.5节和第  7.6节讨论了如何针对更高级的系统模型调整
此基线协议。

1  这种一致性事务的逻辑序列化类似于处理器内核中指令执行的逻辑序列化。
即使当内核执行乱序执
行时, 它仍会按程序顺序提交(序列化)指令。
Machine Translated by Google

112  7.  窥探一致性协议

7.2.1  高级协议规范
基线协议只有三个稳定状态:M、
S  和  I。
这样的协议通常称为  MSI  协议。
与  6.3节中的协议一样,
该协议采用回写缓存。除非块处于状态  M  的缓存中,否则块由  LLC/内存拥有。 在介绍详细规范
之前,
我们首先说明协议的更高级别抽象, 以了解其基本行为。在图7.1和7.2  中, 我们分别展示
了缓存和内存控制器的稳定状态之间的转换。

需要注意三个符号问题。 首先,
在图7.1  中,
弧线标有在总线上观察到的一致性请求。 我们
有意省略其他事件, 包括加载、存储和一致性响应。 其次,
缓存控制器上的一致性事件被标记为
“Own”
或“Other”,以表示观察请求的缓存控制器是否是请求者。 第三,
在图7.2  中,
我们指
定内存中块的状态

其他‑GetM
或者
米 Own‑GetM
自主推销

其他GetS
Own‑GetM

小号

其他‑GetM  或替
换(无声) 自己获取

图  7.1:  MSI:
缓存控制器稳定状态之间的转换。


得到S
或者 得到M
普特姆

我或小号

图  7.2:  MSI:
内存控制器稳定状态之间的转换。
Machine Translated by Google

7.2.基线监听协议  113

使用以缓存为中心的表示法(例如,
内存状态  M  表示存在一个缓存,
其中块处于状态  M)。

7.2.2  简单的监听系统模型:
原子请求、
原子事务

图7.3说明了简单的系统模型, 它与图2.1  中介绍的基准系统模型几乎相同。唯一的区别是图2.1中
的通用互连网络已被指定为总线。 每个核心都可以向其缓存控制器发出加载和存储请求; 当缓存
控制器需要为另一个块腾出空间时, 它会选择一个块来驱逐。 总线促进了所有一致性控制器侦听
的一致性请求的总顺序。 与前一章的例子一样, 这个系统模型具有简化一致性协议的原子性属性。
具体来说, 该系统实现了两个原子性属性, 我们将其定义为原子请求和原子事务。  Atomic  
Requests属性声明一致性请求在发出时的同一周期中被排序。

此属性消除了块状态更改的可能性 由于另一个内核的一致性请求 在发出请求和订购请求之


间。
原子交易

核 核

缓存 私人的 缓存 私人的
控制器 数据缓存 控制器 数据缓存

公共汽车

最后一级
有限责任公司/内存 缓存
控制器
(有限责任公司)

多核处理器芯片

主存

图  7.3:
简单侦听系统模式。
Machine Translated by Google

114  7.  窥探一致性协议

属性指出一致性事务是原子的, 因为对同一块的后续请求可能不会出现在总线上, 直到第一个


事务完成之后(即, 直到响应出现在总线上之后)。 因为一致性涉及对单个块的操作,所以系统
是否允许对不同块的后续请求不会影响协议。 虽然比大多数当前系统更简单, 但该系统模型类
似于  SGI  挑战赛,
这是  1980  年代的成功机器[5]。

详细协议规范表7.5和7.6给出
了简单系统模型的详细一致性协议。 与第  7.2.1  节中的高级描述相比,
最显着的区别是在缓存
控制器中添加了两个瞬态, 在内存控制器中添加了一个。 该协议具有非常少的瞬态,因为简单系
统模型的原子性约束极大地限制了可能的消息交错的数量。

表  7.5:
简单侦听(原子请求、
原子事务):
缓存控制器

巴士活动
处理器核心事件


自己的交易 其他核心的交易
自己的 自己的 自己的 其他 其他 其他
加载存储替换 数据
得到S 得到M 普特姆 得到S 得到M 普特姆
问题 问题
我 得到S 得到M
/ISD /IMD
失速 失速 失速驱逐 将数据复制到 (A) (A) (A)
加载 店铺 缓存中,
加载命
新闻处

/S
失速 失速 失速驱逐 将数据复制到 (A) (A) (A)
加载 店铺 缓存中,
存储命
IMD

/米
负载命 问题 ‑/我 ‑/我
小号 中 得到M
/贴片
负载命 失速 失速驱逐 将数据复制到 (A) (A) (A)
中 店铺 缓存中,
存储命
贴片机

/米
加载 店铺 发出  PutM,
发送 发送数据 发送数据
打 打 数据到 要求和记忆 要求

/我
记忆/我
/S
Machine Translated by Google

7.2.基线监听协议  115

表  7.6:
简单侦听(原子请求、
原子事务):
内存控制器

巴士活动
状态
得到S 得到M 普特姆 来自所有者的数据
发送数据块 发送数据块
IorS 发送给请求者/IorS   给请求者/M的数据
的数据消息 消息
(A) (A) 更新内存/IorS  中的数
IorSD
据块
M‑/I或SD ‑/IorSD

闪回测验问题  6:
在  MSI  侦听协议中,
缓存块只能处于三种一致性状态之一。
对或错?

答:
假的!
由于存在瞬态,
即使对于最简单的系统模型,
状态也不止三种。

表中的阴影条目表示不可能(或至少是错误的)转换。
例如, 一个缓存控制器不应该接收到
一个它没有请求的块的数据消息(即,在它的缓存中处于状态  I  的块)。同样,
原子事务约束会阻
止其他核心在当前事务完成之前发出后续请求;由于此约束,无法出现标记为“(A)” 的表条目。

白条目表示不需要任何操作的合法转换。这些表省略了许多对于理解协议而言不必要的实现细
节。此外,
在本协议和本章的其余协议中,我们省略了与另一个核心事务的数据对应的事件; 一个
核心从不采取任何行动来响应观察总线上另一个核心事务的数据。

对于所有  MSI  协议, 加载可以在状态  S  和  M  中执行(即命中), 而存储仅在状态  M  中命


中。在加载和存储未命中时, 缓存控制器分别通过发送  GetS  和  GetM  请求来启动一致性事务。  
2瞬态ISD、  IMD、  SMD表示请求报文已经发送, 但还没有收到数据响应(Data)。 在这些瞬态
中,因为请求已经被排序, 事务也已经被排序并且块在逻辑上分别处于状态  S、 M  或  M。

但是,
加载或存储必须等待数据到达。
3一旦数据响应出现在

2我们不在此协议中包含升级交易,
这将通过不需要向请求者发送更少的数据来优化  S‑to‑M  转换。
对于具有原子请求的系统模型,添加升级将相当简单,
但如果没有原子请求,
则要复杂得多。 当我们提出一
个没有原子请求的协议时,我们会讨论这个问题。
3从技术上讲,
一旦请求被订购,
就可以执行存储,
只要数据到达时新存储的值没有被覆盖。
同样,
允许
随后加载新写入的值。
Machine Translated by Google

116  7.  窥探一致性协议

总线,
缓存控制器可以将数据块复制到缓存中, 根据需要转换到稳定状态  S  或  M, 并执行挂
起的加载或存储。
系统模型的原子性属性以两种方式简化缓存未命中处理。 首先,  Atomic  Requests
属性确保当缓存控制器试图升级块的权限时 从  I  到  S、
I  到  M,
或  S  到  M 它可以发出请
求而不必担心另一个核心的请求可能会被排序领先于自己。 因此, 高速缓存控制器可以根
据需要立即转换为ISD、  IMD或SMD状态,
以等待数据响应。

同样,
原子事务属性确保在当前事务完成之前不会发生对块的后续请求,
从而消除了在这些
瞬态之一中处理来自其他核心的请求的需要。

数据响应可能来自内存控制器或另一个具有状态  M  块的缓存。 具有状态  S  块的缓存
可以忽略  GetS  请求,
因为内存控制器需要响应,
但必须使  GetM  上的块无效要求执行相干
不变量。
具有处于状态  M  的块的缓存必须响应  GetS  和  GetM  请求,
发送数据响应并分别
转换到状态  S  或状态  I。

LLC/内存有两种稳定状态, M  和  IorS,
以及一种瞬态  IorSD。
在状态  IorS  中,
内存控
制器是所有者并响应  GetS  和  GetM  请求, 因为此状态表明没有缓存具有状态  M  中的块。
在状态  M  中,
内存控制器不响应数据, 因为状态  M  中的缓存是所有者并拥有最新的数据副
本。
然而, 状态  M  中的  GetS  意味着缓存控制器将转换到状态  S, 因此内存控制器也必须获
取数据、 更新内存并开始响应所有未来的请求。 它通过立即转换到瞬态  IorSD并等待直到它
从拥有它的缓存中接收数据来实现这一点。

当缓存控制器由于替换决定而驱逐一个块时, 这会导致协议的两种可能的一致性降
级: 从  S  到  I  和从  M  到  I。
在这个协议中, S‑to‑I  降级是在该块被从缓存中逐出, 而没有与其
他一致性控制器进行任何通信。 通常, 只有当所有其他一致性控制器的行为保持不变时, 静
默状态转换才有可能; 例如, 不允许静默驱逐拥有的块。  M  到  I  降级需要通信, 因为块的  
M  副本是系统中唯一有效的副本, 不能简单地丢弃。 因此,另一个一致性控制器(即内存控
制器) 必须改变其状态。 为了替换状态  M  中的块, 缓存控制器在总线上发出  PutM  请求, 然
后将数据发送回内存控制器。 在  LLC, 当  PutM  请求到达时, 块进入状态  IorSD, 然后在数据
消息到达时转换到状态  IorS。  4原子请求属性通过防止可能降级状态的干预请求(例如,
另一个核心的  GetM  请求) 在  PutM  在总线上被订购之前。 同样,  Atomic  Transactions
属性简化了内存控制器

4  我们做出简化假设,
即这些消息不会乱序到达内存控制器。
Machine Translated by Google

7.2.基线监听协议  117

通过阻止对块的其他请求,
直到  PutM  事务完成并且内存控制器准备好响应它们。

运行示例在本节中,
我们将展示系统的示例执行, 以展示一致性协议在常见场景中的行为方式。 我们将在后续部分中使
用此示例来理解协议并强调它们之间的差异。 该示例仅包含一个块的活动,
最初,该块在所有缓存中
处于状态  I,
在  LLC/内存中处于状态  IorS。

在此示例中, 如表7.7  所示, 内核  C1  和  C2  分别发出加载和存储指令, 但未命中同一块。核心  


C1  尝试发出  GetS, 核心  C2  尝试发出  GetM。 我们假设核心  C1  的请求恰好首先被序列化, 并且
Atomic  Transactions属性阻止核心  C2  的请求到达总线, 直到  C1  的请求完成。内存控制器响应  
C1  以在周期  3  上完成事务。 然后, 核心  C2  的  GetM  在总线上被串行化;  C1  使其副本无效,内存
控制器响应  C2  以完成该事务。 最后, C1  发出另一个  GetS。 所有者  C2  响应数据并将其状态更改为  
S。C2  还将数据副本发送到内存控制器, 因为  LLC/内存现在是所有者并且需要该块的最新副本。 执
行结束时, C1  和  C2  处于状态  S,LLC/内存处于状态  IorS。

7.2.3  基线侦听系统模型:
非原子请求、原子事务我们在本章其余大部分内容中使用
的基线侦听系统模型与简单侦听系统模型的不同之处在于它允许非原子请
求。
非原子请求来自许多实现优化,但最常见的原因是在缓存控制器和总线之间插入消息队列(甚
至单个缓冲区)。通过将发出请求的时间与发出请求的时间分开,
协议必须解决简单侦听系统中不
存在的漏洞窗口。
基线侦听系统模型保留了原子事务属性,我们直到第  7.5  节才放宽。

我们在表7.8和7.9中提供了详细的协议规范,
包括所有瞬态。 与第  7.2.2  节中的简单侦听系统
协议相比,最显着的区别是瞬态的数量要多得多。 放宽Atomic  Requests属性会引入许多情况, 在
这些情况下,缓存控制器会在发出其一致性请求和观察其自身在总线上的一致性请求之间观察总
线上另一个控制器的请求。

以  I‑to‑S  转换为例,
缓存控制器发出  GetS  请求并将块的状态从  I  更改为ISAD。
在总线上观
察到请求缓存控制器自己的  GetS  并序列化之前, 块的状态实际上是I。
也就是说, 请求者的
Machine Translated by Google

118  7.  窥探一致性协议

表  7.7:
简单侦听:
示例执行。
所有活动都是一个街区。

请求于
循环核心  C1 核心C2 有限责任公司/内存 总线数据
公共汽车

最初的我 我 IorS

1个
加载未命中; 问
题  GetS/ISD

2个 GetS  (CI)

3个
店小姐; 由于  S  端响应而停止
到  C1  的原子事务

4个 来自LLC的数据/
内存

5  复制数据到缓存;
执行负载/S 发行  GetM/IMD

6个
得到  M  (C2)

7 ‑/我 发送回复至
C2/M

8个 来自LLC的数据/
内存

9 复制数据到缓存;
执行存储/
M
10  负载未命中:
问题  
GetS/ISD

11 得到  (C1)

12 将数据发送到  C1  和  LLC/ ‑/IorSD
mem/S

13 来自  C2  的数据

14  从C2复制数据;
执行负载/S 从复制数据
C2/IorS

块被视为在  I  中;
无法执行加载和存储, 必须忽略来自其他节点的一致性请求。 一旦请
求者观察到自己的GetS, 请求被排序, block逻辑上是S,
但是因为数据还没有到达,所
以无法进行加载。 缓存控制器将块的状态更改为  ISD并等待来自先前所有者的数据响
应。
由于Atomic  Transactions属性,
数据消息是下一个一致性消息(到同一块)。 一
旦数据响应到达, 交易完成, 请求者将块的状态更改为稳定的  S  状态并执行加载。

I‑to‑M  转换的过程与  I‑to‑S  转换类似。
Machine Translated by Google

7.2.基线监听协议  119

表  7.8:
带有原子事务缓存控制器的  MSI  侦听协议。
标记为“(A)”
的阴影条目表示此转换是不可
能的, 因为事务在总线上是原子的。


加 铺
店 品

替 取


自 Own‑
GetM 销


自 GetS

其 GetM

其 ‑ PutM

其 据



自 复

问题
问题
我 获取M/ ‑ ‑ ‑
获取S/ISAD
IMAD
父亲摊位 失速 失速 ‑/ISD ‑ ‑ ‑
ISD失速 失速 失速 (A) (A) ‑/S
IMAD摊位 失速 失速 ‑/IMD ‑ ‑ ‑
IMD失速 失速 失速 (A) (A) ‑/M
问题
小号 打 获取M/ ‑/我 ‑ ‑/我 ‑
SMAD

SMAD命中 失速 失速 ‑/贴片 ‑ ‑/IMAD ‑


贴片命中 失速 失速 (A) (A) ‑/M
发送数据
问题
请求者和 发送数据至 ‑
M命中 打 把M/
我的 请求者/我
内存/秒
发送数据
发送数据到内存/ 请求者和内存/ 向请求者/IIA  发
米娅命中 打 失速
I IIA 送数据

发送
国际投资协会 失速 失速 失速 NoData  到内 ‑ ‑

存/I

从  S  到  M  的过渡说明了在漏洞窗口期间发生状态更改的可能性。 如果内核尝试存储到处
于状态  S  的块, 则缓存控制器会发出  GetM  请求并转换到状态SMAD。
该块有效地保持在状态  
S,
因此负载可能会继续命中, 并且控制器会忽略来自其他内核的  GetS  请求。
然而,如果另一个核
心的  GetM  请求首先被排序, 缓存控制器必须将状态转换为IMAD以防止进一步的加载命中。 正
如我们在边栏中讨论的那样, S‑to‑M  转换期间的漏洞窗口使升级事务的添加变得复杂。
Machine Translated by Google

120  7.  窥探一致性协议

表  7.9:
带有原子事务内存控制器的  MSI  侦听协议。
标记为“(A)”
的阴影条目表示此转换是不可能的,
因为事务在总线上是原子的。

得到S 得到M 来自所有者  NoData  的  PutM  数据

IorS  发送数据到  re 发送数据请求或/M ‑/IorSD

询问者
IorSD  (A) (A) 将数据写入  LLC/ ‑/IorS

内存/IorS
M‑/I或SD ‑ ‑/医学博士

医学博士(A) (A) 将数据写入  LLC/ ‑/M


IorS

边栏:
在没有原子请求的系统中升级事务对于具有原子请求的协议, 升级事务
是缓存从共享过渡到修改的有效方式。  Upgrade  请求使所有共享副本失效,它比发出  GetM  
快得多,
因为请求者只需要等到  Upgrade  被序列化(即总线仲裁延迟)而不是等待数据从  LLC/
内存到达.

但是,如果没有原子请求, 添加升级事务会变得更加困难, 因为在发出请求和序列化请求之


间存在漏洞窗口。 由于在此漏洞窗口期间序列化的  Other‑GetM  或  Other‑Upgrade,
请求者可能
会丢失其共享副本。 这个问题最简单的解决方案是将块的状态更改为等待其自身升级被序列化的
新状态。当它的  Upgrade  被序列化时,这将使其他  S  副本(如果有的话) 失效但不会返回数据,
然后内核必须发出后续的  GetM  请求以转换到  M。

更有效地处理升级很困难, 因为  LLC/内存需要知道何时发送数据。 考虑这样一种情况, 其


中核心  C0  和  C2  共享一个块  A,
并且都试图升级它, 同时核心  C1  试图读取它。  C0  和  C2  发出
升级请求, C1  发出  GetS  请求。 假设它们在总线上序列化为  C0、C1  和  C2。  C0  的升级成功, 因
此  LLC/内存(处于  IorS  状态) 应将其状态更改为  M  但不发送任何数据, 并且  C2  应使其  S  副本
无效。  C1  的  GetS  在  C0  找到处于状态  M  的块,
它以新数据值响应并将  LLC/内存更新回状态  
IorS。  C2的Upgrade终于出现了, 但是因为丢失了共享副本, 所以需要LLC/memory来响应。 不
幸的是, LLC/内存处于  IorS  状态, 无法判断此升级需要数据。 存在解决此问题的替代方案, 但不在
本入门指南的范围内。
Machine Translated by Google

7.2.基线监听协议  121

漏洞窗口还以更重要的方式影响  M  到  I  一致性降级。 为了替换处于状态  M  的块, 缓存控制


器发出  PutM  请求并将块状态更改为MIA; 与7.2.2节中的协议不同, 它不会立即将数据发送到内
存控制器。 在总线上观察到  PutM  之前,
块的状态实际上是  M, 缓存控制器必须响应其他内核对该
块的一致性请求。 在没有干预一致性请求到达的情况下, 缓存控制器通过将数据发送到内存控制
器并将块状态更改为状态  I  来响应观察其自己的  PutM。 如果干预  GetS  或  GetM  请求在  PutM  
被订购之前到达, 高速缓存控制器必须像处于状态  M  一样做出响应, 然后转换到状态  IIA以等待
其  PutM  出现在总线上。一旦它看到它的  PutM, 直觉上,缓存控制器应该简单地转换到状态  I, 因
为它已经放弃了块的所有权。 不幸的是, 这样做会使内存控制器停留在瞬态状态, 因为它也会收到  
PutM  请求。 缓存控制器也不能简单地发送数据, 因为这样做可能会覆盖有效数据。 5解决方案是
缓存控制器在状态IIA看到其  PutM  时向内存控制器发送特殊的  NoData  消息。 向内存控制器告
知它来自非所有者, 并让内存控制器退出其瞬态。 如果接收到  NoData  消息, 内存控制器需要知道
它应该返回到哪个稳定状态, 因此变得更加复杂。 我们通过添加第二个瞬态记忆状态MD  来解决这
个问题。 请注意, 这些瞬态代表了我们通常的瞬态命名约定的例外。 在这种情况下, 状态XD指示内存
控制器在接收到  NoData  消息时应恢复到状态  X(如果接收到数据消息, 则移至状态  IorS)。

7.2.4  运行示例
回到运行示例, 如表7.10  所示,核心  C1  发出一个  GetS,核心  C2  发出一个  GetM。 与前面的示例
(在表7.7  中) 不同, 消除Atomic  Requests属性意味着两个核心都发出它们的请求并更改它们的
状态。 我们假设核心  C1  的请求恰好首先被序列化, 并且Atomic  Transactions属性确保  C2  的请
求在  C1  的事务完成之前不会出现在总线上。  LLC/内存响应完成C1的事务后, 核心C2的GetM在
总线上序列化。  C1  使其副本无效, LLC/内存响应  C2  以完成该交易。 最后, C1  发出另一个  GetS。
当这个  GetS  到达总线时, 所有者  C2  响应数据并将其状态更改为  S。 C2  还将数据副本发送到内
存控制器, 因为  LLC/内存现在是所有者并且需要更新‑  块的日期副本。 执行结束时, C1  和  C2  处于
状态  S,LLC/内存处于状态  IorS。

5  考虑这样一种情况, 其中核心  C1  在  M  中有一个块并发出  PutM,
但核心  C2  执行  GetM,
核心  C3  执行  GetS,

者都在  C1  的  PutM  之前排序。  C2  获取  M  中的块,
修改块,然后响应  C3  的  GetS,
用更新的块更新  LLC/内存。 当最终
订购  C1  的  PutM  时,
写回数据将覆盖  C2  的更新。
Machine Translated by Google

122  7.  窥探一致性协议

表  7.10:
基线侦听:
示例执行

请求于
循环核心  C1 核心C2 有限责任公司/内存 总线数据
公共汽车

1个 发行  GetS/ISAD

2个 发行  GetM/IMAD

3个
得到  (C1)
‑/ISD 发送数据至
4个

C1/IorS

数据来自
5个

有限责任公司/我

从复制数据 得到  M  (C2)
6个

LLC/内存/S

‑/我 ‑/IMD 发送数据至


7
C2/M

数据来自
8个

有限责任公司/我

从复制数据
9
LLC/内存/M

10  期  GetS/ISAD

11 得到  (C1)
‑/ISD 发送数据到  C1  和 ‑/IorSD
12
到  LLC/mem/S

数据来自
13
C2

从复制数据 从  C2/IorS  复
14
C2/S 制数据

7.2.5  协议简化
该协议相对简单,并牺牲了性能来实现这种简单性。
最重要的简化是在总线上使用原子事务。具有原子事务消除了许多可能的转换, 在表中用
“(A)”
表示。
例如,
当一个内核有一个处于IMD  状态的高速缓存块时, 该内核不可能观察
到来自另一个内核的对该块的一致性请求。如果事务不是原子的, 则可能会发生此类事
件,并迫使我们重新设计协议来处理它们,如我们在第7.5  节中所示。
Machine Translated by Google

7.3.添加独占状态  123

另一个牺牲性能的显着简化涉及对处于状态  S  的缓存块的存储请求事件。 在该协议中,缓
存控制器发出  GetM  并将块状态更改为  SMAD 。
更高性能但更复杂的解决方案将使用升级事务,
如前面的侧边栏中所讨论的那样。

7.3  添加独占状态
有许多重要的协议优化, 我们将在接下来的几节中讨论。
更随意的读者可能希望在第一次阅读时跳过或浏览这些部分。 一种非常常用的优化是添加独占  
(E)  状态,
在本节中,我们将描述如何通过使用  E  状态扩充第7.2.3节中的基线协议来创建  MESI  
侦听协议。 回想第6章,
如果缓存有一个处于独占状态的块, 那么该块是有效的、 只读的、干净的、 独
占的(未缓存在其他地方) 并且是被拥有的。缓存控制器可以在不发出一致性请求的情况下悄悄
地将缓存块的状态从  E  更改为  M。

7.3.1  动机
Exclusive  状态几乎用于所有商业一致性协议, 因为它优化了一个常见的情况。 与MSI协议相比,
MESI协议在核先读块再写的情况下具有重要优势。 这是许多重要应用程序(包括单线程应用程
序) 中的典型事件序列。 在  MSI  协议中,
在加载未命中时,缓存控制器将发起  GetS  事务以获取读
取权限; 在后续的store上, 会发起GetM事务获取写权限。 然而,MESI  协议使缓存控制器能够在
状态  E  而不是  S  中获取块, 以防在没有其他缓存访问该块时发生  GetS。

因此,
后续存储不需要  GetM  事务;
缓存控制器可以默默地将块的状态从  E  升级到  M,
并允许核
心写入块。
因此,
在这种常见情况下, E  状态可以消除一半的一致性事务。

7.3.2  进入独占状态
在解释协议如何工作之前, 我们必须首先弄清楚  GetS  的发行者如何确定没有其他共享者,
从而
直接进入状态  E  而不是状态  S  是安全的。
至少有两种可能的解决方案:

‧  向总线添加线或“共享器” 信号: 当在总线上订购GetS  时,


共享块的所有高速缓存控制器
断言“共享器” 信号。
如果  GetS  的请求者观察到“共享者” 信号被断言,
则请求者将其块状
态更改为  S;
否则,请求者将其块状态更改为  E。 此解决方案的缺点是必须实现线或信号。 这
个额外的共享线路在这个已经有共享线路总线的基线监听系统模型中可能没有问题, 但它
会使不使用共享线路总线的实现变得非常复杂(第  7.6  节) 。
Machine Translated by Google

124  7.  窥探一致性协议

‧  在LLC  中维护额外的状态: 一种替代解决方案是让LLC  区分状态I(无共享者) 和S(一个


或多个共享者), 这对于MSI  协议来说是不需要的。 在状态  I  中,
内存控制器以特殊标记为  
Exclusive  的数据进行响应; 在状态  S  中,
内存控制器以未标记的数据响应。

然而, 准确地保持  S  状态具有挑战性, 因为  LLC  必须检测到最后一个共享者何时放弃其副


本。 首先, 这要求缓存控制器在驱逐处于状态  S  的块时发出  PutS  消息。 其次,内存控制器必
须维护共享器的计数作为该块状态的一部分。 这比我们以前的协议复杂得多, 带宽密集度也
更高, 后者允许静默驱逐  S  中的块。 一个更简单但不太完整的替代方案允许  LLC  保守地跟
踪共享者; 也就是说, 内存控制器的状态  S  意味着在状态  S  中有零个或多个缓存。 缓存控制
器静默地替换状态  S  中的块, 因此即使在最后一个共享器被替换后  LLC  仍保持在  S  中。 如
果状态  M  中的块被写回(使用  PutM), 则  LLC  块的状态变为  I。
这种“保守的  S”
解决方
案放弃了一些使用  E  状态的机会(即, 当最后一个共享者先于另一个共享者替换其副本时
core  issues  a  GetM),
但它避免了显式  PutS  交易的需要, 并且仍然捕获许多重要的共享模
式。

在本节介绍的  MESI  协议中,
我们选择了最可实现的选项 在  LLC  中保持保守的  S  状态
既避免了与在高速总线中实现线或信号相关的工程问题, 又避免了显式  PutS  事务。

7.3.3  协议的高级规范
在图7.4和7.5  中, 我们展示了  MESI  协议中稳定状态之间的转换。  MESI  协议在高速缓存和  
LLC/内存方面均不同于基线  MSI  协议。 在缓存中,GetS  请求转换为  S  或  E,
具体取决于订购  
GetS  时  LLC/内存中的状态。然后, 从状态  E,块可以静默更改为  M。 在这个协议中, 我们使用  
PutM  来驱逐  E  中的块,而不是使用单独的  PutE; 此决定有助于保持协议规范简洁, 并且对协议
功能没有影响。

LLC/内存比  MSI  协议中的状态更稳定。  LLC/内存现在必须区分由零个或多个缓存共享
的块(保守的  S  状态)和根本不共享的块(I), 而不是像  MSI  协议中那样将它们合并为一个状
态.

在本入门教程中,
我们将  E  状态视为所有权状态,
它对协议有重大影响。
但是,
有些协议不
将  E  状态视为所有权状态,侧边栏讨论了此类协议中涉及的问题。
Machine Translated by Google

7.3.添加独占状态  125

其他‑GetM Own‑GetM

或者

自主推销
其他GetS

沉默的

Own‑GetM

其他GetS
其他‑GetM
或者

自主推销
Own‑GetS(我
小号

的内存)

其他‑GetM  或替
换(无声)
Own‑GetS(内
存不在  I  中)

图  7.4:  MESI:
缓存控制器稳定状态之间的转换。

企业经营管理

得到S
得到M

小号

普特姆

GetS  


或  GetM

图  7.5:  MESI:
内存控制器稳定状态之间的转换。

边栏:
如果  E  是非所有权状态,
MESI  监听在内存控
制器将块提供给处于状态  E  的高速缓存之后的请求。  Be‑
Machine Translated by Google

126  7.  窥探一致性协议

由于从状态  E  到状态  M  的转换是静默的, 内存控制器无法知道缓存是否持有  E  中的块,在
这种情况下  LLC/内存是所有者, 或者在  M  中,
在这种情况下缓存是所有者。
如果此时  GetS  
或  GetM  在总线上被序列化, 缓存可以很容易地确定它是否是所有者并且应该响应, 但内存
控制器不能做出同样的决定。

此问题的一种解决方案是让  LLC/内存等待缓存响应。 当  GetS  或  GetM  在总线上被
序列化时,具有处于状态  M  的块的缓存以数据响应。 内存控制器等待一段固定的时间, 如果
在该时间窗口内没有出现响应, 则内存控制器推断它是所有者并且它必须响应。 如果确实出
现来自缓存的响应,则内存控制器不会响应一致性请求。 该解决方案有几个缺点, 包括可能增
加内存响应的延迟。一些实现以增加内存带宽、 功率和能量为代价, 通过推测性地从内存中预
取块来隐藏部分或全部延迟。 一个更重要的缺点是必须将系统设计为缓存的响应延迟是可
预测的并且很短。

7.3.4  详细说明

在表7.11和7.12  中,
我们给出了  MESI  协议的详细规范,
包括瞬态。与  MSI  协议的差异以粗体突
出显示。
该协议仅将稳定  E  状态和瞬态  EIA  添加到缓存状态集, 但还有更多的  LLC/内存状态, 包括一个
额外的瞬态。
此  MESI  协议共享基线  MSI  协议中存在的所有相同简化。一致性事务仍然是原子的, 等等。

7.3.5  运行示例

我们现在回到正在运行的示例, 如表7.13  所示。
执行几乎立即不同于  MSI  协议。 当  C1  的  GetS  
出现在总线上时, LLC/内存处于状态  I,
因此可以发送  C1  Exclusive  数据。  C1  观察总线上的  
Exclusive  数据并将其状态更改为  E(而不是  MSI  协议中的  S)。 其余的执行过程与  MSI  示例
类似,
只是瞬态差异很小。

7.4  添加拥有状态

第二个重要的优化是  Owned  状态,
在本节中, 我们将描述如何通过使用  O  状态扩充第7.2.3节
中的基线协议来创建  MOSI  侦听协议。回想第6章,如果缓存有一个处于  Owned  状态的块,那么
该块是有效的、
只读的、 脏的, 并且缓存是所有者, 即缓存必须响应
Machine Translated by Google

7.4.添加拥有的状态  127

表  7.11:  MESI  侦听协议 缓存控制器。
标记为“(A)”
的阴影条目表示此转换是不可能的,
因为
事务在总线上是原子的。


加 铺
店 品

替 GetS

拥 GetM

拥 销


自 GetS

其 GetM

其 ‑ PutM

其 响




自应 (家)





自 独

问题 问题
我 获取/ 获取M/ ‑ ‑ ‑
父亲们 IMAD
父亲摊位 失速 失速‑/ISD ‑ ‑ ‑

ISD失速 失速 失速 (A) (A) (A)  ‑/S  ‑/E


IMAD摊位 失速 失速 ‑/IMD ‑ ‑ ‑

IMD失速 失速 失速 (A) (A) (是


问题
小号 打 获取M/ ‑/我 ‑ ‑/我 ‑
SMAD
SMAD命中 失速 失速 ‑/贴片 ‑ ‑/IMAD ‑

贴片命中 失速 失速 (A) (A) ‑/M


发送数据
问题 发送数据
E  命中/M 把M/ 请求者和 到 ‑

就是这个
请求者/我
内存/秒
发送数据
问题
到请求者和内存/ 发送数据给请求
M命中 打 把M/ ‑
S 者/我
我的

发送数据
发送数据至
将数据发送到请求者内存/I  和
米娅命中 打 失速 ‑
请求者/
国际投资协会

内存/IIA
发送数据
发送编号 发送数据给请求
这个命中 失速 失速 数据‑E  到内 请求者和 ‑
者/
存/I IIA内
存/IIA
发送编号
国际投资协会 失速 失速 失速 数据到内 ‑ ‑ ‑

存/I
Machine Translated by Google

128  7.  窥探一致性协议

表  7.12:  MESI  侦听协议 内存控制器。
标有“(A)”
的阴影条目表示此转换是不可能的,
因为
事务在总线上是原子的。

得到S 得到M 推特数据 无数据无数据‑E


向请求者发送数据 向请求者发送数据 ‑/ID
/EorM /EorM

向请求者发送数据 ‑/标清
小号
向请求者发送数据 /EorM
EorM‑/SD ‑ ‑/EorMD
将数据写入
身份证
(A) (A) (A) ‑/我 ‑/我
记忆/我
写数据到内存/S
标准差 (A) (A) (A) ‑/S ‑/S

写数据到内存/I
欧洲医学博士(A) (A) (A) ‑/EorM  ‑/我

块的一致性请求。
我们维护与基线侦听  MSI  协议相同的系统模型;
事务是原子的,
但请求不是原
子的。

7.4.1  动机

与  MSI  或  MESI  协议相比,
添加  O  状态在一个特定且重要的情况下是有利的: 当缓存具有处于
状态  M  或  E  的块并从另一个内核接收到  GetS  时。在7.2.3节的MSI协议和7.3节的MESI协议中,
缓存必须将块状态从M或E变为S , 并将数据发送给请求者和内存控制器。 数据必须发送给内存控
制器, 因为响应缓存放弃所有权(通过降级到状态  S) 并且  LLC/内存成为所有者, 因此必须具有
最新的数据副本以响应后续请求。

添加  O  状态有两个好处:(1)
它消除了当缓存在  M(和  E)状态下接收到  GetS  请求时更
新  LLC/内存的额外数据消息, 以及(2)它消除了可能不必要的写入LLC(如果块在写回  LLC  之
前再次写入)。 从历史上看,对于多芯片多处理器, 还有第三个好处, 即  O  状态允许后续请求由缓
存而不是慢得多的内存来满足。 如今, 在具有包容性  LLC  的多核中,
正如本入门中的系统模型一
样, LLC  的访问延迟几乎不及片外  DRAM  存储器的访问延迟。 因此,
让缓存响应而不是  LLC  不如
让缓存响应而不是内存带来的好处大。

我们现在提出一个  MOSI  协议并展示它如何实现这两个好处。
Machine Translated by Google

7.4.添加拥有的状态  129

表  7.13:  MESI:
示例执行

请求于
循环核心  C1 核心C2 有限责任公司/内存 总线数据
公共汽车

1个 发行  GetS/ISAD

2个 发行  GetM/IMAD

3个
得到  (C1)
发送排除
4  ‑/ISD 重要的数据
C1/EorM

独家的
5个 数据来自
有限责任公司/我

从复制数据
6个
得到  M  (C2)
有限责任公司/我/E

7秒 结束数据到  C2/I  ‑/MD ‑/EorM
8个 来自  C1  的数据

从复制数据
9
C1/M

10  期  GetS/ISAD

11 得到  (C1)
发送数据到  C1
12  ‑/ISD 并 ‑/标清
LLC/内存/S

13 来自  C2  的数据

从复制数据 从  C2/S  复制
14
C2/S 数据

7.4.2  高级协议规范
我们在图7.6和7.7中指定了稳定状态之间转换的高级视图。
关键区别在于当具有状态  M  的块的高速缓存从另一个核心接收到  GetS  时会发生什么。
在  
MOSI  协议中,缓存将块状态更改为  O(而不是  S)
并保留块的所有权(而不是将所有权转移
到  LLC/内存)。因此,
O  状态使缓存能够避免更新  LLC/内存。
Machine Translated by Google

130  7.  窥探一致性协议

其他‑GetM

或者

自主推销
其他GetS Own‑GetM

Own‑GetM

其他‑GetM
或者

自主推销

Own‑GetM
小号

其他‑GetM  或替
换(无声) 自己获取

图  7.6:  MOSI:
缓存控制器稳定状态之间的转换。

摩尔

得到M
普特姆

IorS

图  7.7:  MOSI:
内存控制器稳定状态之间的转换。

7.4.3  详细协议规范

在表7.14和7.15  中,
我们给出了  MOSI  协议的详细规范,
包括瞬态。 与  MSI  协议的差异
以粗体突出显示。
除了稳定的  O  状态之外, 该协议还添加了两个瞬态缓存状态。 瞬态  OIA状态有助于处理
状态  O  中块的替换, 瞬态  OMA状态处理存储后升级回状态  M。内存控制器没有额外的
瞬态, 但我们将  M  状态重命名为  MorO, 因为内存控制器不需要区分这两种状态。
Machine Translated by Google

7.4.添加拥有的状态  131

表  7.14:  MOSI  侦听协议  ‑  缓存控制器。
标记为“(A)”
的阴影条目表示此转换是不可能的,
因为
事务在总线上是原子的。


加 铺
店 品

替 取


自 Own‑
GetM 销


自 GetS

其 GetM

其 ‑ PutM

其 响




自应

问题  GetS 发行GetM ‑ ‑ ‑

/  父亲们 /IMAD
父亲摊位 失速 失速 ‑/ISD ‑ ‑ ‑
ISD失速 失速 失速 (A) (A) (作为
IMAD摊位 失速 失速 ‑/IMD ‑ ‑ ‑
IMD失速 失速 失速 (A) (A) (是
发行GetM ‑ ‑
小号 打 ‑/我 ‑/我
/SMAD
SMAD命中 失速 失速 ‑/贴片 ‑ ‑/IMAD ‑
贴片命中 失速 失速 (A) (A) (是
发行GetM 发行PutM 发送数据至 发送数据至 ‑
O命中
/  自己的 /他是 请求者 请求者/我
发送数据至
发送数据至 ‑
自己命中 失速 失速 ‑/M 请求者
请求者 /IMAD

发行量 发送数据给请求 发送数据至 ‑


M命中 打
/我的 者/O 请求者/我

发送数据 发送数据至
发送数据至
米娅命中 打 失速 记忆中 请求者
/我 /他是 请求者 /IIA  ‑

发送数据 发送数据至
发送数据至 ‑
是命中 失速 失速 记忆中 请求者
/我
请求者 /IIA
发送编号
国际投资协会
失速 失速 失速 数据到内 ‑ ‑ ‑

存/I

为了使规范尽可能简洁, 我们将  PutM  和  PutO  交易合并为一个  PutM  交易。
也就是说,
缓存使用  PutM  逐出状态  O  的块。
该决定对协议的功能没有影响, 但有助于保持表格规范的可读性。

此  MOSI  协议共享基线  MSI  协议中存在的所有相同简化。
一致性事务仍然是原子的,

等。
Machine Translated by Google

132  7.  窥探一致性协议

表  7.15:  MOSI  侦听协议  ‑  内存控制器。
标有“(A)”
的阴影条目表示此转换是不可能的,
因为
事务在总线上是原子的。

得到S 得到M 普特姆 来自所有者  NoData  的数据

IorS 发送数据至 发送数据至 ‑/IorSD

请求者 请求者/MorO
IorSD (A) (A) 将数据写入 ‑/IorS

内存/IorS
摩尔O‑ ‑ ‑/莫德

莫德(A) (A) 将数据写入 ‑/摩尔

内存/IorS

7.4.4  运行示例

在表7.16  中, 我们返回到为  MSI  协议引入的运行示例。 该示例以与  MSI  示例相同的方式进行, 直


到  C1  的第二个  GetS  出现在总线上。
在  MOSI  协议中, 这第二个  GetS  导致  C2  响应  C1  并将其状态更改为  O(而不是  S)。  C2  保
留块的所有权并且不需要将数据复制回  LLC/内存(除非并且直到它驱逐块, 未显示)。

7.5  非原子总线
基线  MSI  协议以及  MESI  和  MOSI  变体都依赖于Atomic  Trans  actions假设。
这种原子性大
大简化了协议的设计, 但牺牲了性能。

7.5.1  动机

实现原子事务的最简单方法是使用带有原子总线协议的共享线总线; 也就是说,
所有总线事务都
由不可分割的请求‑响应对组成。 拥有原子总线类似于拥有非流水线处理器内核;无法重叠可以并
行进行的活动。图7.8说明了原子总线的操作。

因为一致性事务占用总线直到响应完成,所以原子总线平凡地实现了原子事务。
然而,总线的吞吐
量受到请求和响应延迟总和的限制(包括请求和响应之间的任何等待周期,未显示)。考虑到片
外存储器可以提供响应,这种延迟瓶颈会影响总线性能。

图7.9说明了流水线、
非原子总线的操作。
关键优势是在总线上序列化后续请求之前不必等
待响应,因此总线可以使用同一组共享线路实现更高的带宽。如何‑
Machine Translated by Google

7.5.非原子总线  133

表  7.16:  MOSI:
示例执行

请求于
循环核心  C1  (C1) 核心  C2  (C2) 有限责任公司/内存 总线数据
公共汽车

1个 发行  GetS/ISAD

2个 发行  GetM/IMAD

3个
得到  (C1)
发送数据至
4个 ‑/ISD
C1/IorS

数据来自
5个

有限责任公司/我

从复制数据
6个
得到  M  (C2)
LLC/内存/S

发送数据至
7 ‑/我 ‑/IMD
C2/二氧化钼

数据来自
8个

有限责任公司/我

从复制数据
9
LLC/内存/M

10 发行  GetS/ISAD

11 得到  (C1)
12 ‑/ISD 发送数据到  C1/O  ‑/MorO

13 来自  C2  的数据

从复制数据
14
C2/S

曾经,
实现原子事务变得更加困难(但并非不可能)。 原子事务属性将并发事务限制在同一个块,
而不
是不同的块。  SGI  Challenge  在流水线总线上强制执行原子事务,
使用快速表查找来检查同一块的
另一个事务是否已经挂起。

7.5.2  有序与有序乱序响应
非原子总线的一个主要设计问题是它是流水线还是拆分事务。
如图7.9  所示,
流水线总线以与请求相
同的顺序提供响应。  A
Machine Translated by Google

134  7.  窥探一致性协议

地址总线 请求  1 请求  2 请求  3

数据总线 响应  1 响应  2 响应  3

图  7.8:
原子总线。

地址总线 请求  1  请求  2  请求  3

数据总线
响应1 响应  2 响应  3

图  7.9:
流水线(非原子)
总线。

地址总线 请求  1  请求  2  请求  3

数据总线 响应2 响应  3 响应  1

图  7.10:
拆分事务(非原子)
总线。

拆分事务总线,
如图7.10所示,
可以以不同于请求顺序的顺序提供响应。

拆分事务总线相对于流水线总线的优势在于, 低延迟响应不必等待对先前请求的长延迟
响应。
例如,如果请求  1  是针对内存拥有的块且不存在于  LLC  中,
而请求  2  是针对片上缓存拥有
的块,
则强制响应  2  等待响应  1,
因为流水线总线需要,  会导致性能损失。

拆分事务总线引发的一个问题是将响应与请求进行匹配。对于原子总线,
很明显响应对应
于最近的请求。使用流水线总线,请求者必须跟踪未完成请求的数量以确定哪个消息是对其请
求的响应。对于拆分事务总线,
响应必须携带请求或请求者的身份。

7.5.3  非原子系统模型

我们假设一个如图7.11  所示的系统。
请求总线和响应总线是分离的,独立运行。 每个一致性控制
器与两条总线都有连接, 但内存控制器没有连接来发出请求。
我们绘制  FIFO  队列来缓冲传入和
传出消息,
因为在一致性协议中考虑它们很重要。 值得注意的是,如果一致性控制器在处理来自
请求总线的传入请求时停止, 那么它后面的所有请求
Machine Translated by Google

7.5.非原子总线  135

核 核

缓存 缓存
缓存 缓存
控制器 控制器

请求总线
响应总线

记忆
有限责任公司
控制器

图  7.11:
具有拆分事务总线的系统模型。

(在停滞的请求之后序列化)将不会被该一致性控制器处理, 直到它处理当前停滞的请求。
无论消息类型或地址如何,
这些队列都以严格的  FIFO  方式处理。

7.5.4  带有拆分事务总线的  MSI  协议
在本节中,
我们修改了基线  MSI  协议,
以便在具有拆分事务总线的系统中使用。
拥有拆分事
务总线不会改变稳定状态之间的转换, 但对详细实现有很大影响。
特别地,
还有更多可能

过渡。

在表7.17和7.18  中,
我们指定了协议。
现在可以实现原子总线无法实现的几种转换。 例
如,
缓存现在可以接收它在状态  ISD  中的块的  Other‑GetS 。
所有这些新的可能转换都是针
对缓存等待数据响应的瞬态块; 在等待数据时,缓存首先观察到该块的另一个一致性请求。
回想一下7.1节,事务的排序是基于其请求在总线上的排序时间, 而不是数据到达请求者的
时间。

因此,
在这些新的可能转换中的每一个中,
缓存已经有效地完成了它的事务,
但恰好还没有
数据。
回到我们的ISD示例,
Machine Translated by Google

136  7.  窥探一致性协议

表  7.17:
带有拆分事务总线的  MSI  侦听协议 缓存控制器


加 铺
店 品

替 或



自 Own‑
GetM Own‑
GetM 销


自 GetS

其 GetM

其 ‑ PutM

其 响




自 己

(对
应 求)


问题  GetS 发行GetM ‑ ‑ ‑

/  父亲们 /IMAD
父亲摊位 失速 失速 ‑/ISD ‑ ‑ ‑  ‑/一

‑ 加载
ISD失速 失速 失速 失速
命中/秒

负载命
伊萨失速 失速 失速 ‑ ‑
中/S
IMAD摊位 失速 失速 ‑/IMD ‑ ‑ ‑  ‑/有
商店命
IMD失速 失速 失速 失速 失速
中率/M
商店命
有个摊位 失速 失速 ‑ ‑
中率/M
发行GetM ‑
小号 打 ‑/我 ‑/我
/SMAD
SMAD命中 失速 失速 ‑/贴片 ‑ ‑/IMAD ‑/SMA
店铺
贴片命中 失速 失速 失速 失速
命中/米

商店命
SMA命中 失速 失速 ‑ ‑/有
中率/M

发送数据至
问题
请求者和 发送数据给请求
M命中 打 普特姆
/我的 者 /I  到内存
/S
发送数据给请求
发送数据 发送数据至
米娅命中 打 失速 重新
者和请求者到内存

探索者/我 /IIA
/IIA

国际投资协会
失速 失速 失速 ‑/我 ‑ ‑ ‑
Machine Translated by Google

7.5.非原子总线  137

表  7.18:
带有拆分事务总线的  MSI  侦听协议 内存控制器

把M从 把M从
得到S 得到M 数据
所有者 非所有者

发送数据至 发送数据至 ‑

IorS 请求者 requestor,


设置Owner
为requestor/M
清除所有者 将所有者设置为 清除 ‑ 将数据写入内存/

/IorSD 请求者 所有者/IorSD IorSA
失速 失速 失速 ‑ 将数据写入
IorSD
内存/IorS
清除所有者 ‑ 清除 ‑
SA
/IorS 所有者/IorS

高速缓存块实际上在  S  中。因此,
处于此状态的  Other‑GetS  的到达不需要采取任何操
作,
因为在  S  中具有块的高速缓存不需要响应  Other‑GetS。
然而,除了上述示例之外的新可能转换更为复杂。 当在总线上观察到  Other‑GetS  
时,
考虑缓存中处于  IMD  状态的块。
缓存块实际上处于状态  M, 因此缓存是块的所有者, 但还没有块的数据。 因为缓存是所有
者,
所以缓存必须响应Other‑GetS,而缓存只有在收到数据后才能响应。 对这种情况最简
单的解决方案是让缓存停止对  Other‑GetS  的处理,直到数据响应到达其  Own‑GetM。
此时,缓存块将变为状态  M, 并且缓存将有有效数据发送给  Other‑GetS  的请求者。

对于其他新的可能转换,在缓存控制器和内存控制器上,我们也选择暂停,
直到数据
到达以满足飞行中的请求。
这是最简单的方法,但它引发了三个问题。首先,
它牺牲了一些
性能,
我们将在下一节讨论。

其次,拖延会增加僵局的可能性。
如果控制器在等待另一个事件(消息到达)时可以
在消息上停止,那么架构师必须确保等待的事件最终会发生。摊位的循环链会导致死锁,
必须避免。在本节的协议中,停止的控制器保证接收到取消停止它们的消息。
这种保证很
容易看出,因为控制器已经看到了自己的请求,停顿只影响请求网络,
控制器正在等待响应
网络上的数据消息。

延迟一致性请求引起的第三个问题是,也许令人惊讶的是,
它使请求者能够在处理
自己的请求之前观察对其请求的响应。考虑
Machine Translated by Google

138  7.  窥探一致性协议

表  7.19  中的示例。 核心  C1  为块  X  发出  GetM, 并将  X  的状态更改为IMAD。


C1  在总线上观察其  GetM  并将状态更改为IMD。  LLC/内存是  X  的所有者, 需要很长时间才
能从内存中检索数据并将其放到总线上。 同时, 核心  C2  为在总线上序列化但不能被  C1  处理
(即, C1  停止) 的  X  发出  GetM。  C1  为块  Y  发出  GetM, 然后在总线上序列化。  Y  的这个  
GetM  在  C1  之前停滞的一致性请求(来自  C2  的  GetM) 之后排队,因此  C1  无法处理自己
的  Y  的  GetM。但是, 所有者  C2  可以处理  Y  的这个  GetM  并快速响应  C1 .因此, C1  可以在处
理其请求之前观察对  Y  的  GetM  的响应。 这种可能性需要添加瞬态。 在本例中, 核心  C1  将块  
Y  的状态从IMAD更改为IMA。 同样, 该协议也需要添加瞬态  ISA和SMA。 在这些瞬态中, 在请求
之前观察到响应, 块实际上处于先前状态。 例如, IMA  中的一个块在逻辑上处于状态  I, 因为尚
未处理  GetM; 如果块在  IMA  中, 缓存控制器不会响应观察到的  GetS  或  GetM 。 我们将  IMA
与IMD  进行对比 在IMD  中, 块在逻辑上位于  M  中, 一旦数据到达, 缓存控制器必须响应观
察到的  GetS  或  GetM  请求。

该协议与本章中之前的协议还有一个不同之处, 即与  PutM  交易有关的不同之处。 处理
方式不同的情况是, 当一个核心(例如核心  C1) 发出  PutM,并且来自另一个核心的  GetS  或  
GetM  在  C1  的  PutM  之前对同一块进行排序。  C1在观察到自己的  PutM  之前从状态  MIA  
转换到  IIA 。 在本章前面的原子协议中, C1  观察自己的  PutM  并向  LLC/内存发送  NoData  消
息。  NoData  消息通知  LLC/内存  PutM  事务已完成(即, 它不必等待数据)。 在这种情况
下,C1  无法向  LLC/内存发送数据消息, 因为  C1  的数据已过时, 协议无法发送  LLC/内存过时
数据, 这些数据随后会覆盖数据的最新值。 在本章的非原子协议中, 我们在  LLC  中的每个块的
状态都增加了一个字段, 该字段保存块当前所有者的身份。  LLC  在每次更改区块所有权的交
易中更新区块的所有者字段。 使用所有者字段, LLC  可以识别在公共汽车上订购非所有者的  
PutM  的情况; 这与  C1在观察其  PutM  时处于状态  IIA  的情况完全相同。 因此, LLC  知道发生
了什么, C1  不必向  LLC  发送  NoData  消息。

为了简单起见, 与原子协议相比, 我们选择修改  PutM  事务在非原子协议中的处理方式。 允许  


LLC  直接识别这种情况比要求使用  NoData  消息更简单; 使用非原子协议, 系统中可能存在
大量  NoData  消息,
并且  NoData  消息可以在其关联的  PutM  请求之前到达。
Machine Translated by Google

7.5.非原子总线  139

表  7.19:
示例:
请求前的响应。
最初,
块  X  在两个缓存中都处于状态  I,
块  Y  在核心  C2  中
处于状态  M。

请求于 资料于
循环核心  C1  (C1) 核心  C2  (C2) 有限责任公司/内存
公共汽车 公共汽车

X:我 X:我 X:我


最初的
义 Y:M Y:M
X:
店小姐; 问题
1个

获取M/IMAD
2个 X:
GetM(C1)

X:
进程GetM
X:  进程  GetM  (C1) / X:  进程  GetM  (C1)  ‑  忽略 (C1)  ‑  LLC  未命中,
开始
3个

IMD 从  DRAM  访问  X

X:
店小姐; 问题
4个

获取M/  IMAD

Y:
商店小姐; 问题
5个
X:
GetM(C2)
获取M/  IMAD

X:  进程  GetM  (C2) X:
进程GetM
6个
X:
在  GetM  (C2)  上失速 Y:
GetM(C1)
/  国际多媒体学院
(C2)  ‑  忽略

7 Y:  进程  GetM  (C1)  ‑  向  C1/I   Y:
进程GetM
Y:
队列  GetM  (C1) 发送数据 (C1)  ‑  忽略
Y:
数据来自
8个

C2
Y:
将数据写入缓存/
9

X:  LLC小姐
10 完成,
向C1发送数据

X:
数据来自
11
有限责任公司

X:
写入数据到缓存/M;
12
执行商店

X:
(卸载)
进程
13 GetM  (C2)  ‑  发送数据到  C2/I

Y:
进程(按顺序) X:
数据来自
14 获取  M  (C1)/M ;按表格存 C1

X:
写入数据
15
高速缓存/M;
执行商店
Machine Translated by Google

140  7.  窥探一致性协议

7.5.5  具有拆分事务总线的优化的非停顿  MSI  协议

如前一节所述, 我们通过使用拆分事务总线在系统的新可能转换上停止来牺牲一些性能。
例如, 具有处于  ISD  状态的块的高速缓存停止而不是为该块处理  Other‑GetM。
然而,在  
Other‑GetM  之后可能有一个或多个请求,到其他块,缓存可以处理而不会停止。 通过停止
请求, 协议会在停止的请求之后停止所有请求, 并延迟这些事务的完成。 理想情况下, 我们
希望连贯性控制器在停滞的请求之后处理请求, 但回想一下 为了支持内存请求的总顺序
监听需要连贯性控制器按照接收到的顺序观察和处理请求。 不允许重新排序。

这个问题的解决方案是按顺序处理所有消息, 而不是停顿。 我们的方法是添加瞬态,


以反映连贯性控制器已收到但必须记住在以后的事件中完成的消息。 回到ISD中缓存块的
例子,如果缓存控制器在总线上观察到一个Other‑GetM, 那么它将块状态更改为ISDI  (表
示“在I,
去S,
等待数据, 当数据到达将去我”)。 类似地, IMD  中接收到  Other‑GetS  的块
将状态更改为IMDS ,
并且必须记住  Other‑GetS  的请求者。 当数据响应缓存的GetM到达
时,
缓存控制器将数据发送给Other‑GetS的请求者, 并将块的状态更改为S。

除了瞬态的激增之外, 非停顿协议还引入了潜在的活锁问题。 考虑一个在IMDS中有


一个块的缓存, 该块接收数据以响应其  GetM。 如果缓存立即将块状态更改为  S  并将数据
发送给  Other‑GetS  的请求者,则它不会执行它最初为其发出  GetM  的存储。 如果核心随
后重新发出  GetM, 同样的情况可能会反复出现, 并且存储可能永远不会执行。 为了保证不
会出现这种活锁, 我们要求ISDI、  IMDI、  IMDS或IMDSI中的缓存(或具有附加稳定一致
性状态的协议中的任何类似状态) 在接收到数据时执行一次加载或存储到块它的请求。 6在
执行一次加载或存储之后, 它可能会更改状态并将块转发到另一个缓存。 我们将对活锁的
更深入处理推迟到第9.3.2  节。

我们在表7.20和7.21中给出了非停顿  MSI  协议的详细规范。
最明显的区别是瞬态的
数量。
这些状态本身并不复杂, 但它们确实增加了协议的整体复杂性。

我们没有从内存控制器中移除这些停顿, 因为它不可行。
考虑  IorSD  中的一个块。
内存控制器观察到来自核心  C1  的  GetM  并且当前停止。

6当且仅当该加载或存储是程序顺序中最早的加载或存储时, 才必须执行加载或存储
一致性请求是最先发出的。我们将在第  9.3.2  节中更详细地讨论这个问题。
Machine Translated by Google

7.5.非原子总线  141

表  7.20:
使用拆分事务总线的优化  MSI  侦听     缓存控制器


加 铺
店 品

替 取


自 Own‑
GetM 销


自 GetS

其 GetM

其 ‑ PutM

其 据



自 复

问题  GetS 发行GetM
/IMAD

‑ ‑ ‑
/  父亲们
父亲摊位
新闻处 失速 失速‑/ISD ‑ ‑ ‑  ‑/一个
失速 失速 失速 ‑ ‑/ISDI 负载命中/S

加载
失速 失速 失速 ‑ .
命中/秒
丁 ‑
是我 失速 失速 失速 . 负载命中/I
IMAD摊位 失速 失速 ‑/IMD ‑ . ‑  ‑/有
IMD失速 失速 失速 ‑/IMDS ‑/现在 商店命中率/M
店铺
有个摊位 失速 失速 ‑ ‑ ‑
命中/米
存储命中,
将数据发
IMDI档位 失速 失速 ‑ ‑ 送到  GetM

请求者/我
存储命中,
将数据发送到  
IMDS失速 失速 失速 ‑ ‑/IMDSI GetS  请求者和  mem/S

存储命中,
将数据发
IMDSI失速 失速 失速 ‑ ‑ 送到  GetS  请求器和  

mem/I
发出  
GetM/
SMAD
小号 打 ‑/我 ‑ ‑/我

SMAD命中 失速 失速 ‑/贴片 ‑ ‑/IMAD ‑/SMA


贴片命中 失速 失速 ‑/SMDS ‑/SMDI 商店命中率/M
店铺
SMA命中 失速 失速 ‑ ‑/有
命中/米
存储命中,
将数据发
SMDI命中 失速 失速 ‑ ‑ 送到  GetM

请求者/我
存储命中,
将数据发送到  
SMDS命中 失速 失速 ‑ ‑/SMDSI GetS  请求者和  mem/S

存储命中, 将数据
SMDSI命中 失速 失速 ‑ ‑ 发送到  GetS  re
questor  和  mem/I
问题 发送数据至
发送数据至
M命中 打 把M/ 请求者和内存/S
请求者/我
我的
发送 发送数据至
数据到 内存请求者和请求 发送数据至
米娅命中 打 失速
者 请求者/IIA
国际投资协会 /我 /IIA
失速 失速 失速 ‑/我 ‑ ‑ ‑
Machine Translated by Google

142  7.  窥探一致性协议

表  7.21:
使用拆分事务总线优化的  MSI  侦听  ‑  内存控制器

把M从 把M从
得到S 得到M 数据
所有者 非所有者
发送数据至 ‑
发送数据给请求者,
设置
IorS
请求者 所有者到请求者/M
清除所有者 清除所有者 ‑ 将数据写入
将所有者设置为请求者

/IorSD /IorSD 内存/IorSA
将数据写入
IorSD失速 失速 失速 ‑
内存/IorS
清除所有者 清除所有者
SA ‑ ‑
/IorS /IorS

然而,
看起来我们可以在等待数据时简单地将块的状态更改为  IorSDM。 然而, 在  IorSDM  
中,
内存控制器可以观察到来自核心  C2  的  GetS。
如果内存控制器没有在此  GetS  上停止, 它必
须将块状态更改为IorSDMIorSD。
在此状态下, 内存控制器可以观察到来自核心  C3  的  GetM。

没有优雅的方法可以将  LLC/内存所需的瞬态数量限制为一个较小的数量(即,
小于内核数
量),
因此,为简单起见,我们让内存控制器停顿。

7.6  公交互联网络优化

到目前为止,
在本章中我们假设了系统模型,其中存在用于一致性请求和响应的单个共享线路
总线或用于请求和响应的专用共享线路总线。在本节中,
我们将探讨另外两种可以提高性能的可
能系统模型。

7.6.1  数据响应的独立非总线网络

我们已经强调了侦听系统提供广播一致性请求的总顺序的必要性。
表7.2中的示例显示了缺乏
一致性请求的总顺序是如何导致不一致的。然而,
没有必要订购一致性响应, 也没有必要广播它
们。
因此,一致性响应可以在不支持广播或排序的单独网络上传播。
这样的网络包括交叉、 网格、
圆环、蝴蝶等。

使用单独的非总线网络进行一致性响应有几个优点。
Machine Translated by Google

7.6.公交互联网络优化  143

‧  可实施性:
很难实施高速共享线总线,
特别是对于总线上有许多控制器的系统。
其他拓扑可以
使用点对点链接。

‧  吞吐量:
总线一次只能提供一个响应。
其他拓扑一次可以有多个动态响应。

‧  延迟:
使用总线进行一致性响应要求每个响应都会产生延迟以对总线进行仲裁。
其他拓扑可
以允许立即发送响应而无需仲裁。

7.6.2  一致性请求的逻辑总线侦听系统要求存在广播一致性请求的总
顺序。用于一致性请求的共享线路总线是实现广播总顺序的最直接方式,但这并不是唯一的方式。
有两种方法可以在没有物理总线的情况下实现与总线(即逻辑总线)相同的完全有序的广播属性。

‧  具有物理全序的其他拓扑: 共享线总线是实现广播全序的最明显的拓扑, 但也存在其他拓扑。


一个值得注意的例子是一棵树, 在树的叶子上有一致性控制器。 如果所有一致性请求都被单播
到树的根部, 然后向下广播树, 那么每个一致性控制器都会观察到相同的一致性广播总顺序。
此拓扑中的序列化点是树的根。  Sun  Microsystems  在其  Starfire  多处理器  [3]  中使用树
形拓扑, 我们将在第  7.7  节中详细讨论。

‧  逻辑全序:
即使没有自然提供这种顺序的网络拓扑, 也可以获得广播的全序。关键是按逻辑时
间对请求进行排序。 马丁等人。  [6]设计了一个监听协议, 称为时间戳监听,
可以在任何网络拓
扑上运行。 为了发出一致性请求,缓存控制器将其广播到每个一致性控制器, 并用广播消息应
该被排序的逻辑时间标记广播。 协议必须确保  (a)  每个广播都有一个不同的逻辑时间,(b)  一
致性控制器按逻辑时间顺序处理请求(即使它们在物理时间上不按此顺序到达), 以及  (c)  
在逻辑时间没有请求T  可以在控制器经过逻辑时间  T  后到达控制器。

阿加瓦尔等。
提出了一种类似的方案,
称为网络内侦听排序(INSO)  [1]。

闪回测验问题  7:
侦听高速缓存一致性协议要求内核在总线上进行通信。
对或错?

答:
假的!
侦听需要一个完全有序的广播网络,
但该功能可以在没有物理总线的情况下实现。
Machine Translated by Google

144  7.  窥探一致性协议

7.7  案例研究

我们展示了两个真实世界的监听系统示例:
Sun  Starfire  E10000  和  IBM  Power5。

7.7.1  太阳星火  E10000

Sun  Microsystems  的  Starfire  E10000  [3]是一个带有侦听协议的商业系统的有趣示例。
一致性
协议本身并不那么引人注目; 该协议是典型的带有回写缓存的  MOESI  监听协议。  E10000  的与众
不同之处在于它是如何设计成最多可扩展到  64  个处理器的。 建筑师根据我们依次讨论的三个重要
观察结果进行了创新。

首先, 共享线侦听总线不能扩展到大量内核, 这主要是由于电气工程限制。 针对这一观察结果,


E10000  仅使用点对点链路而不是总线。  E10000  不是在物理(共享线路) 总线上广播一致性请
求,而是在逻辑总线上广播一致性请求。 窥探协议背后的关键洞察力是它们需要一致性请求的总顺
序,但这种总顺序不需要物理总线。 如图7.12  所示,  E10000  将逻辑总线实现为一棵树,其中处理
器是树叶。 树中的所有链接都是点对点的, 因此不需要总线。 处理器将请求单播到树的顶部, 在那里
它被序列化, 然后向下广播树。由于根部的序列化, 树提供了完全有序的广播。 一个给定的请求可能
在不同的时间到达两个处理器, 这很好;重要的约束是处理器遵守相同的请求总顺序。

E10000  架构师的第二个观察是,
通过使用多个(逻辑) 总线可以实现更大的一致性请求带
宽,
同时仍然保持一致性请求的总顺序。  E10000  有四个逻辑总线,一致性请求在它们之间交错处
理。
通过要求处理器以固定的、 预先确定的顺序侦听逻辑总线来强制执行总顺序。

第三,架构师观察到比请求消息大得多的数据响应消息不需要一致性请求所需的完全有序的
广播网络。

许多先前的侦听系统实现了数据总线, 它不必要地提供广播和总排序,
同时限制带宽。
为了提
高带宽,E10000  将数据网络实现为交叉开关。
再一次,有点对点链路而不是总线,
交叉开关的带宽
远远超过总线(物理或逻辑) 可能达到的带宽。

E10000  的架构针对可扩展性进行了优化,
这种优化设计需要架构师对非原子请求和非原子
事务进行推理。
Machine Translated by Google

7.7.案例研究  145

逻辑总线根
播送 播送
转变
请求下来 请求下来

单播
转变 转变
请求到根

转变 转变 转变 转变

过程 过程 过程 过程 过程 过程 过程 过程

交叉数据网络

图  7.12:  Starfire  E10000(为清楚起见仅绘制了八个处理器)。
一致性请求在向下广播到所有
处理器之前单播到根, 在那里它被序列化。

7.7.2  IBM  POWER5
IBM  Power5  [8]是一个双核芯片,
其中两个内核共享一个二级缓存。 每个  Power5  芯片都有一个
结构总线控制器  (FBC), 可以将多个  Power5  芯片连接在一起以创建更大的系统。 大型系统最多包
含八个节点, 其中每个节点都是一个具有四个  Power5  芯片的多芯片模块  (MCM)。

抽象地看, IBM  Power5  似乎使用了一个相当典型的  MESI  监听协议,在拆分事务总线上实
现。
然而,这种简单的描述遗漏了几个值得讨论的独特功能。 特别是, 我们关注两个方面:互连网络的
环形拓扑和  MESI  相干性的新变体的添加

状态。

环上的窥探一致性Power5  使用
的互连网络与我们目前讨论的完全不同, 这些差异对一致性协议有重要影响。 最重要的是,Power5  
使用三个单向环连接节点, 这些环用于承载三种类型的消息:
请求、 侦听响应/决策消息和数据。 单向
环不提供全序,除非要求所有消息都从环上的同一节点开始, 而  Power5  不需要。相反,
请求者在环
上发送请求消息,然后在看到请求在穿过整个环后返回时吸收该请求。

每个节点观察环上的请求,
节点中的每个处理器确定其监听
Machine Translated by Google

146  7.  窥探一致性协议

回复。
观察请求的第一个节点提供单个探听响应, 该响应是该节点上所有处理器的聚合探听响应。探
听响应不是实际的数据响应,而是对芯片或节点将采取的操作的描述。如果没有完全有序的网络,
芯片/
节点就无法立即采取行动,
因为它们可能无法就如何响应做出一致的决定。 探听响应在探听响应环上
传播到下一个节点。
该节点类似地产生单个探听响应, 该响应聚合第一节点的探听响应加上第二节点
上所有处理器的探听响应。

当所有节点的聚合监听响应到达请求芯片时,请求芯片决定每个处理器应该做什么来响应请求。请求
者芯片将此决定沿环路广播到每个节点。该决定消息由环中的每个节点/芯片处理,
并且已被确定为提
供数据响应的节点/芯片将该数据响应发送到数据环上的请求者。

由于缺乏完全有序的互连网络,该协议比典型的侦听协议复杂得多。 该协议仍然具有一致性请
求的总逻辑顺序,但如果没有完全有序的网络, 节点将无法立即响应请求, 因为请求在总顺序中的位置
尚未确定它何时出现在网络上。尽管很复杂,但  Power5  设计提供了仅具有点对点链路和环状拓扑结
构简单性的优势(例如,环状结构中的路由非常简单, 以至于切换速度比其他拓扑结构更快)。已经有
其他协议利用环拓扑并探索环的排序问题[2,  4,  7]。

一致性状态的额外变体Power5  协
议从根本上说是一个  MESI  协议,但其中一些状态具有多种“风味”。 我们在表  7.22  中列出了所有状
态。
我们希望强调两个新状态。 首先,有共享状态的  SL  变体。
如果一个  L2  缓存在状态  SL  中持有一个
块,
它可能会从同一节点上的处理器向  GetS  响应数据,
从而减少该事务的延迟并减少片外带宽需求;
这种提供数据的能力将  SL  与  S  区分开来。

另一个有趣的新状态是  T(agged)  状态。
当块处于  Modified  并收到  GetS  请求时进入  T  状态。
缓存不会降级为  S(它会在  MESI  协议中执行) 或  O(它会在  MOSI  协议中执行), 而是将状态更改
为  T。
状态  T  中的块类似于  O  状态,
因为它有一个比内存中的值更新的值, 并且在其他缓存中可能有也
可能没有状态  S  中的块的副本。

与  O  状态一样,块可以在  T  状态下读取。
令人惊讶的是, T  状态有时被描述为读写状态,
这违反了  
SWMR  不变量。 事实上,
状态  T  的存储可能会立即执行, 因此确实违反了实时(物理) 时间的  SWMR  
不变性。 然而,
该协议仍然在基于环顺序的逻辑时间内强制执行  SWMR  不变量。 虽然这种排序的细节
超出了本入门书的范围, 但我们认为将  T  状态视为  E  状态的变体会有所帮助。 回想一下  E  状态允许
Machine Translated by Google

7.7.案例研究  147

表  7.22:  Power5  L2  高速缓存一致性状态

状态权限说明
我 没有任何 无效的
小号
Read‑only  Shared  Read‑
SL only  Shared  local  data  source,  but  can  response  with  data  to  the  requests  from  processors  in  
the  same  node  (有时称为  F  状态,
如  Intel  QuickPath  协议  (Section  8.8.4) )

小号(小号) Read‑only  Shared  Me  
(E)  Read‑write  Exclusive  M  (M)  Read‑
write  Modified  Mu  Read‑write  Modified

响应读取的读写数据
只请求Read‑
吨 only  TM,
收到GetS。  T  有时被描述为读写状态,
这违反了  SWMR  不变量,
因为在状态  S  中也有块。
更好的
理解  T  的方式是它像  E:
它可以立即转换到  M。
但是,
与E,
这个转换不是无声的:
存储到  T  
状态中的块立即转换到  M  但(原子地)
在环上发出无效消息。
尽管其他缓存可能会与此
请求竞争,
但  T  状态具有优先权,
因此可以保证首先排序,
因此不需要等待无效完成。

向  M  的无声过渡; 因此,只要状态(原子地) 转换到状态  M, 就可以立即执行对状态  E  中块的存储。 T  状态类似;


状态  T  中的存储立即转换到状态  M。 但是, 由于状态  S  中也可能存在副本, 因此状态  T  中的存储也会立即在环上
发出无效消息。 其他内核可能正在尝试从  I  或  S  升级到  M,
但  T  状态充当一致性排序点, 因此具有优先级, 无需等
待确认。 目前尚不清楚该协议是否足以支持  SC  和  TSO  等强内存一致性模型; 然而,正如我们在第5  章中讨论的
那样, Power  内存模型是最弱的内存一致性模型之一。 这种标记状态优化了生产者消费者共享的常见场景, 其中
一个线程写入一个块, 然后一个或多个其他线程读取该块。 生产者可以重新获得读写访问权限, 而不必每次都等
待那么久。
Machine Translated by Google

148  7.  窥探一致性协议

7.8  讨论和窥探的未来

侦听系统在早期的多处理器中很流行,
因为它们以简单着称,而且它们缺乏可扩展性对于主导市场的
相对较小的系统来说并不重要。
侦听还为不可扩展的系统提供性能优势,因为每个侦听事务都可以用
两条消息完成,
我们将与目录协议的三消息事务进行对比。

尽管有很多优点,但窥探已不再常用。即使对于小规模系统,
窥探缺乏可扩展性不是问题,窥
探也不再常见。
与满足目录协议的低成本互连网络相比,窥探对完全有序的广播网络的要求成本太高。此外,
对于可
扩展系统,窥探显然不适合。具有大量内核的系统可能会受到广播请求所需的互连网络带宽和监听每
个请求所需的一致性控制器带宽的瓶颈。 对于这样的系统,
需要一个更具可扩展性的一致性协议,正
是这种可扩展性的需求最初激发了我们在下一章中介绍的目录协议。

窥探可能是解决方案的一部分,即使对于可扩展的系统也是如此。
正如我们将在9.1.6  节中看
到的,解决规模问题的一种强大技术是分而治之。例如,
一个由多个多核芯片组成的系统可以使用芯
片内的窥探一致性协议和跨芯片的可扩展目录协议来保持一致性。

7.9  参考资料

[1]  N.  Agarwal,  L.‑S.  Peh  和  NK  Jha。  In‑network  snoop  ordering  (INSO):无序互连上的  
Snoopy  一致性。 在过程中。 第  14  届国际高性能计算机体系结构研讨会, 第  67–78  页,
2009  年  
2  月。 DOI:  10.1109/hpca.2009.4798238。  143

[2]  LA  Barroso  和  M.  Dubois。
开槽环上的高速缓存一致性。
在过程中。  20号
并行处理国际会议,  1991  年  8  月。 146

[3]  A.查尔斯沃思。  Starfire: 扩展  SMP  信封。  IEEE微,  18(1):39–49,
1998  年  1  月/2  月。
DOI:
10.1109/40.653032。  143,  144

[4]  S.  Frank、 H.  Burkhardt,  III  和  J.  Rothnie。  KSR1:
弥合共享内存和  MPP  之间的差距。 在过
程中。 第  38  届年度  IEEE  计算机协会计算机会议  (COMPCON), 第  285–95  页,
1993  年  2  月。
DOI: 10.1109/cmpcon.1993.289682。  146

[5]  M.  Galles  和  E.  Williams。  SGI  Challenge  多处理器的性能优化、 实现和验证。 在过程中。



威夷国际系统科学会议,  1994.  DOI: 10.1109/hicss.1994.323177。  114
Machine Translated by Google

7.9.参考文献  149

[6]  Martin  MMK、 Sorin  DJ、Ailamaki  A、
Alameldeen  AR、 Dickson  RM、
CJ
Mauer、 KE  Moore、M.  Plakal、
MD  Hill  和  DA  Wood。
时间戳侦听: 一种扩展  SMP  的方法。
在过程中。 第  9  届编程语言和操作系统架构支持国际会议, 第25‑36  页,2000  年  11  月。

DOI:
10.1145/378993.378998。  143

[7]  马蒂先生和希尔博士。
基于环的芯片多处理器的一致性排序。 在过程中。 第  39  届  IEEE/ACM  
微架构国际研讨会,  2006  年  12  月。
DOI:
10.1109/micro.2006.14。  146

[8]  B.  Sinharoy、
RN  Kalla、
JM  Tendler、
RJ  Eickemeyer  和  JB  Joyner。  POWER5  系统微
架构。  IBM  研究与开发杂志,  49(4/5), 2005  年  7  月/9  月。
DOI:10.1147/rd.494.0505。  
145
Machine Translated by Google
Machine Translated by Google

151

第  8  章

目录一致性协议
在本章中,我们介绍了目录一致性协议。
最初开发目录协议是为了解决侦听协议缺乏可扩展性的
问题。传统的侦听系统在完全有序的互连网络上广播所有请求,
并且所有请求都被所有相干控制
器侦听。相比之下,
目录协议使用间接级别来避免有序广播网络和让每个缓存控制器处理每个请
求。

我们首先介绍较高级别的目录协议(第8.1  节)。 然后,我们展示了一个具有完整但不复杂
的三态  (MSI)  目录协议的系统(第8.2  节)。
该系统和协议作为我们稍后添加系统功能和协议优化的基线。 然后,我们将解释如何将独占状态
(第8.3  节)
和拥有状态(第8.4  节)
添加到基线  MSI  协议中。接下来我们讨论如何表示目录状态
(第8.5  节)
以及如何设计和实现目录本身(第8.6  节)。 然后,我们描述了提高性能和降低实施
成本的技术(第8.7  节)。 然后我们讨论带有目录协议的商业系统(第8.8  节) , 然后在讨论目
录协议及其未来(第8.9  节) 结束本章。

那些只满足于学习目录一致性协议基础知识的读者可以浏览或跳过第8.3节到第8.7  节,

管这些部分中的一些材料将帮助读者更好地理解第8.8  节中的案例研究。

8.1  目录协议简介

目录协议的关键创新是建立一个目录, 该目录维护每个块的一致性状态的全局视图。 目录跟踪哪些


缓存保存每个块以及处于什么状态。 想要发出一致性请求(例如  GetS)
的缓存控制器将其直接
发送到目录(即单播消息), 目录查找块的状态以确定下一步要采取的操作。 例如,目录状态可能
指示所请求的块由核心C2  的缓存拥有, 因此应将请求转发给C2(例如, 使用新的Fwd‑GetS  请
求)以获得该块的副本。
当  C2  的缓存控制器收到这个转发的请求时, 它会向请求的缓存控制器单
播一个响应。

比较目录协议和侦听协议的基本操作是很有启发性的。在目录协议中,
目录维护每个块的
状态,
缓存控制器将所有请求发送到目录。该目录要么响应请求,要么将请求转发给一个或多个随
后响应的其他一致性控制器。一致性事务通常涉及两个步骤(单播请求,随后是单播响应)
或三个
Machine Translated by Google

152  8.目录一致性协议

步骤(一个单播请求,K  1  个转发请求,
和  K  个响应,
其中  K  是共享者的数量)。有些协议甚至有第四
步,
因为响应间接通过目录, 或者因为请求者在事务完成时通知目录。 相比之下,侦听协议将块的状态分
布到可能所有的一致性控制器上。

因为没有这种分布式状态的集中总结,
一致性请求必须广播到所有一致性控制器。
因此,
窥探一致性
事务总是涉及两个步骤(一个广播请求,然后是一个单播响应)。

与侦听协议一样,目录协议需要定义一致性事务何时以及如何相对于其他事务进行排序。
在大
多数目录协议中,一致性事务是在目录中排序的。
多个一致性控制器可能同时向目录发送一致性请求,
事务顺序由请求在目录中序列化的顺序决定。如果两个请求争先恐后地访问该目录,
互连网络会有效
地选择该目录首先处理哪个请求。

第二个到达的请求的命运取决于目录协议和竞争的请求类型。 第二个请求可能  (a)  在第一个请求之后
立即处理, (b)  在等待第一个请求完成时保留在目录中, 或  (c)  否定确认  (NACKed)。
在后一种情况下,
目录向请求者发送否定确认消息  (NACK), 请求者必须重新发出请求。 在本章中, 我们不考虑使用  
NACK  的协议, 但我们在第9.3.2  节中讨论了  NACK  的可能使用以及它们如何导致活锁问题。

使用目录作为排序点表示目录协议和侦听协议之间的另一个关键区别。 传统的侦听协议通过
序列化有序广播网络上的所有事务来创建总序。  Snooping  的全序不仅保证了每个块的请求按块顺
序处理,而且便于实现内存一致性模型。 回想一下, 传统的侦听协议使用完全有序的广播来序列化所有
请求;
因此, 当一个请求者观察到它自己的一致性请求时, 这就作为它的一致性时期可能开始的通知。
特别是,当一个窥探控制器看到它自己的  GetM  请求时, 它可以推断出其他缓存将使它们的  S  块无效。
我们在表7.4中演示了此序列化通知足以支持强  SC  和  TSO  内存一致性模型。

相比之下,
目录协议对目录中的事务进行排序,以确保所有节点按块顺序处理冲突请求。然而,
缺少全序意味着目录协议中的请求者需要另一种策略来确定其请求何时被序列化,从而确定其一致性
时期何时可以安全地开始。因为(大多数)
目录协议不使用完全有序的广播,所以没有全局的序列化概
念。

相反,
必须针对(可能)具有块副本的所有缓存单独序列化请求。 需要显式消息来通知请求者其请求已
被每个相关缓存序列化。特别是,
在  GetM  请求中,
每个缓存控制器都有一个
Machine Translated by Google

8.2.基线目录系统  153

一旦序列化了失效消息,
共享  (S)  副本必须发送显式确认  (Ack)  消息。

目录和侦听协议之间的这种比较突出了它们之间的基本权衡。目录协议以间接级别(即,对
于某些事务具有三个步骤,而不是两个步骤)为代价,
实现了更大的可扩展性(即,因为它需要更
少的带宽)。这种额外的间接级别增加了一些一致性事务的延迟。

8.2  基线目录系统
在本节中,
我们展示了一个基线系统,该系统具有简单明了、
适度优化的目录协议。
该系统提供了
对目录协议的关键特性的洞察,
同时揭示了激发本章后续部分中介绍的特性和优化的低效率。

8.2.1  目录系统模型
我们在图8.1  中说明了我们的目录系统模型。 与侦听协议不同, 互连网络的拓扑结构是有意模糊
的。 它可以是网格、 环面或架构师希望使用的任何其他拓扑。 我们在本章中假设的互连网络的一个
限制是它强制执行点对点排序。 也就是说,
如果控制器  A  向控制器  B  发送两条消息,
那么消息到
达控制器  B  的顺序与它们发送的顺序相同。1点对点排序降低了协议的复杂性, 我们推迟讨论在
第  8.7.3节之前没有订购的网络。

这个目录系统模型和图2.1中的基线系统模型之间的唯一区别是我们添加了一个目录并且
我们将内存控制器重命名为目录控制器。 有多种调整目录大小和组织目录的方法, 现在我们假设
最简单的模型:对于内存中的每个块, 都有一个对应的目录条目。在第  8.6  节中,我们检查并比较
了更实用的目录组织选项。 我们还假设一个具有单个目录控制器的单体  LLC; 在第  8.7.1  节中,

们解释了如何在  LLC  的多个库和多个目录控制器之间分配此功能。

8.2.2  高级协议规范
基线目录协议只有三种稳定状态:
MSI。
除非块处于状态  M  的缓存中,
否则块由目录控制器拥有。
每个块的目录状态包括稳定一致性状态、 所有者的身份(如果块处于状态  M)和

1严格来说,
我们只要求某些类型的消息采用点对点顺序,
但我们将此细节推迟到第8.7.3  节。
Machine Translated by Google

154  8.目录一致性协议

核 核

私人的 私人的
缓存 缓存
控制器
数据(L1) 控制器
数据(L1)
缓存 缓存

互联网络

有限责任公司/目录 最后一级
目录
控制器 缓存

多核处理器芯片

主存

图  8.1:
目录系统模型。

编码为单热位向量的共享者的身份(如果块处于状态  S)。
我们在图  8.2  中说明了目录条目。
在  8.5  节中,
我们将讨论目录条目的其他编码。

2  位Log2N  位 N位

状态 所有者 分享者列表(one‑hot  bit  vector)

图  8.2:
具有  N  个节点的系统中块的目录条目。

在介绍详细规范之前, 我们首先说明协议的更高层次抽象, 以了解其基本行为。 在图8.3  


中,我们展示了高速缓存控制器发出一致性请求以将权限从  I  更改为  S、 I  或  S  更改为  M、
M  更
改为  I、
S  更改为  I  的事务。与上一章中的侦听协议一样, 我们使用以缓存为中心的符号指定块
的目录状态(例如, 目录状态  M  表示存在一个缓存,
其中块处于状态  M)。 请注意, 缓存控制
器可能不会默默地逐出共享块; 也就是说,有一个明确的  PutS  请求。我们推迟讨论
Machine Translated by Google

8.2.基线目录系统  155

静默驱逐共享块的协议,
以及静默与显式  PutS  请求的比较,
直到第8.7.4  节。

大多数交易都相当简单, 但有两笔交易值得在这里进一步讨论。 第一个是当高速缓存尝试


按任务从  I  或  S  升级到  M  并且目录状态为  S  时发生的事务。 高速缓存控制器向目录发送  GetM,
目录执行两个操作。 首先, 它用包含数据和“AckCount” 的消息响应请求者;  AckCount  是区
块当前共享者的数量。 该目录将  AckCount  发送给请求者, 以通知请求者有多少共享者必须确认
已使他们的块无效以响应  GetM。 其次,目录向所有当前共享者发送无效  (Inv)  消息。 每个共享者
在收到  Invalidation  后,向请求者发送  Invalidation‑Ack  (Inv‑Ack)。
一旦请求者收到来自目录
的消息和所有的  Inv‑Ack  消息, 它就完成了交易。 收到所有  Inv‑Ack  消息的请求者知道该块不再
有任何读者, 因此它可以在不违反一致性的情况下写入该块。

值得进一步讨论的第二个事务发生在缓存试图驱逐处于状态  M  的块时。 在该协议中, 我们
让缓存控制器向目录发送包含数据的  PutM  消息。
目录以  Put‑Ack  响应。
如果  PutM  没有携带
数据, 那么协议将需要第三条消息 从缓存控制器到目录的数据消息, 该目录包含处于状态  M  的
被逐出的块 在  PutM  事务中发送。
此目录协议中的  PutM  事务不同于侦听协议中发生的事务,
其中  PutM  不携带数据。

8.2.3  避免死锁
在此协议中,消息的接收会导致一致性控制器发送另一条消息。一般来说,
如果事件  A(例如,

息接收)
可以导致事件  B(例如,
消息发送)
并且这两个事件都需要资源分配(例如, 网络链接和
缓冲区),那么我们必须小心避免可能发生的死锁如果出现循环资源依赖。

例如, GetS  请求可以导致目录控制器发出  Fwd‑GetS  消息;
如果这些消息使用相同的资源(例
如, 网络链接和缓冲区), 那么系统可能会死锁。在图8.4  中,我们展示了一个死锁,
其中两个一致
性控制器  C1  和  C2  正在响应彼此的请求,
但传入队列中已经充满了其他一致性请求。 如果队列
是  FIFO,则响应无法通过请求。

由于队列已满,每个控制器都会停止尝试发送响应。
因为队列是  FIFO,
所以控制器无法切换以处
理后续请求(或获得响应)。
于是,
系统死锁。
避免一致性协议死锁的一个众所周知的解决方案是为每一类消息使用单独的网络。 这些网
络可以是物理上分离的或逻辑上分离的(称为虚拟网络),但关键是避免消息类之间的依赖性。
Machine Translated by Google

156  8.目录一致性协议

(1)  获取 (1)  获取 (2)  Fwd‑GetS


要求 要求 你 所有者
我→小号
我→小号 我→小号 中号→小号 中号→小号
S→S
(3)  资料

(2)  资料
(3)  资料
从  I  到  S  的过渡

(1)  得到M (1)  得到M (2)  Fwd‑GetM

要求 你 要求 你 所有者
我→男 我→男 我→男 米→米 米→我

(2)  数据[ack=0]
(3)  数据[ack=0]

(3)  Inv‑Ack
(1)  GetM  (2)  Inv
分享者
要求 S→I 唯一的共享者可能是请求者, 在这种情况

我→男 下, 不会发送  Invalidation  消息, 并且从  Dir  
小号→中号
小号→中号 到  Req  的  Data  消息的  AckCount  为零。
(2)  投资 分享者
S→I
(2)  数据[ack>0]

(3)  Inv‑Ack
从  I  或  S  到  M  的过渡

(1)
PutM+数据 (1)  看跌期权


要求 你 要求 S→I
米→我 米→我 S→I
S→S

(2)  Put‑Ack (2)  Put‑Ack
从  M  或  S  到  I  的过渡

图  8.3:  MSI  目录协议的高级描述。
在每次转换中,
请求事务的缓存控制器表示为“Req”。
Machine Translated by Google

8.2.基线目录系统  157

满满的要求
数据响应
C1

满满的要求
数据响应
C2

图  8.4:
死锁示例。

数据响应
C1
满满的要求

满满的要求

C2
数据响应

图  8.5:
避免分离网络的死锁。

图8.5说明了一个系统,
其中请求和响应消息在不同的物理网络上传输。
因为一个响应不能被另
一个请求阻塞, 它最终会被它的目的节点消耗掉,
从而打破了循环依赖。

本节中的目录协议使用三个网络来避免死锁。 因为一个请求可以引起一个转发的请求, 一
个转发的请求可以引起一个响应, 所以有三种消息类别, 每种消息类别都需要它们自己的网络。 请
求消息有  GetS、GetM  和  PutM。转发的请求消息有Fwd‑GetS、
Fwd‑GetM、
Inv(alidation)、
Put  
Ack。
响应消息是  Data  和  Inv‑Ack。
本章中的协议要求转发请求网络提供点对点排序; 其他网络
没有顺序约束, 在不同网络上传输的消息之间也没有任何顺序约束。

我们推迟对死锁避免进行更彻底的讨论,包括更多解释
虚拟网络的定义和避免死锁的确切要求,直到第9.3  节。
Machine Translated by Google

158  8.目录一致性协议

8.2.4  详细协议规范
我们在表8.1和8.2中提供了详细的协议规范,包括所有瞬态。 与第  8.2.2  节中的高级描述
相比,
最显着的区别是瞬态。 一致性控制器必须管理处于一致性事务中的块的状态, 包括
缓存控制器在将其一致性请求发送到目录和接收其所有必要响应消息之间从另一个控
制器接收转发请求的情况, 包括数据和可能的  Inv‑Acks。

缓存控制器可以在核心用于跟踪未完成的一致性请求的未命中状态处理寄存器  (MSHR)  
中维护此状态。 在符号上, 我们以XYAD  的形式表示这些瞬态,其中上标  A  表示等待确认,
上标  D  表示等待数据。  (此表示法不同于侦听协议, 其中上标  A  表示等待请求出现在
总线上。)

表  8.1:  MSI  目录协议 缓存控制器


加 铺
店 品

替 Fwd‑
GetS GetM
Fwd‑ 资
投 认
确 自


数 你 0)
=确
(认 自


数 你0)
(认
>确   自


数 者

所 答
应 认




我 将  GetS  发送 发送
到  Dir/ISD 得到M到
目录/IMAD
新闻处 失速 失速 失速 失速 ‑/S ‑/S
IMAD摊位 失速 失速 失速 失速 ‑/M  ‑/IMA  ‑/M  ack‑‑  ack‑‑  ‑/M
有个摊位 失速 失速 失速 失速
小号 打 发送 将  PutS  发送 发送发票
得到M到 到  Dir/SIA 确认
目录/SMAD 要求/我
SMAD命中 失速 失速 失速 失速 发送发票 ‑/M  ‑/SMA 确认‑‑
确认

请求/IMAD
SMA命中 失速 失速 失速 失速 确认‑‑  ‑/M
M命中 打 发送PutM 发送数据 发送数据
+数据到 到  Req  和到  Req/I
总监/MIA 目录/S
米娅摊位 失速 失速 发送数据 发送数据 ‑/我

Req  和 到  Req/IIA到  
总监/新航
是 失速 失速 失速 发送发票 ‑/我

确认

请求/IIA
国际投资协会 失速 失速 失速 ‑/我
Machine Translated by Google

8.2.基线目录系统  159

表  8.2:  MSI  目录协议 目录控制器

把  M+ 把M+数据
推特
得到S 得到M 放在最后 数据来自 来自非 数据
不是最后
所有者 所有者

我发送数据到 发送数据至 发送确认 发送确认 发送确认

Req, 将  Req  添加 Req,


将  Owner  设 要求 要求 要求
到  Sharers/S 置为  Req/M
S  发送数据至 发送数据至 从共享者中删 从共享者中删 从共享者中删
Req,
将  Req  添加 Req,
将  Inv  发送 除  Req,
发送   除  Req,
发送   除  Req,
发送  
到  Sharers 给  Sharers,
清除   Put‑Ack Put‑Ack Put‑Ack
Sharers,
将   要求 要求/我 要求
Owner  设置为

要求/米
M  转发 发送转发 发送确认 发送确认 复制数据 发送确认
去 得到M到 要求 要求 到内存,清除 要求
楼主,
加 所有者,
集合 所有者,发送  
要求和 业主要求 Put
业主给 确认请求/我
分享者,清晰
所有者/SD

小号 失速 失速 从共享者中删 从共享者中删 从共享者中删 复制数据到内
除  Req,
发送   除  Req,
发送   除  Req,
发送   存/S
Put‑Ack Put‑Ack Put‑Ack

要求 要求 要求

由于这些表格乍一看可能有些令人生畏,
下一节将介绍一些示例场景。

8.2.5  协议操作
该协议使缓存能够获取状态  S  和  M  中的块,
并在这些状态中的任何一个中将块替换到目
录中。

I  到  S(常见情况#1)
缓存控制器向目录发送  GetS  请求并将块状态从  I  更改为ISD。
目录收到这个请求,如果目
录是所有者(即当前没有缓存在  M  中有块), 目录用数据消息响应, 将块的状态更改为  S
(如果它还不是  S),
并且将请求者添加到共享者列表。 当数据到达请求者时, 缓存控制器
将块的状态更改为  S,
完成事务。
Machine Translated by Google

160  8.目录一致性协议

I  到  S(常见情况  #2)
缓存控制器向目录发送  GetS  请求并将块状态从  I  更改为ISD。
如果该目录不是所有者(即,
有一个缓存当前在  M  中有该块), 该目录将请求转发给所有者并将该块的状态更改为暂态  
SD 。
所有者通过向请求者发送数据并将块的状态更改为  S  来响应此  Fwd‑GetS  消息。
现在
以前的所有者也必须向目录发送数据, 因为它正在放弃对目录的所有权, 目录必须有一个更新
块的最新副本。当数据到达请求者时, 缓存控制器将块状态更改为  S  并认为事务已完成。 当数
据到达目录时,目录将其复制到内存, 将块状态更改为  S, 并认为事务完成。

I  到  S(竞赛案例)
以上两种  I‑to‑S  场景代表了常见的情况,
其中正在进行的块只有一个交易。
该协议的大部分
复杂性源于必须处理一个块的多个正在进行的事务的不太常见的情况。 例如,读者可能会惊
讶于缓存控制器可以接收到处于  ISD  状态的块的无效。

考虑发出  GetS  并转到  ISD  的核心  C1和另一个发出  GetM  的核心  C2,
用于在  C1  的  GetS  
之后到达目录的同一块。 该目录首先发送  C1  数据以响应其  GetS,然后发送无效以响应  C2  
的  GetM。
因为  Data  和  Invalidation  在不同的网络上传输,
它们可能会乱序到达, 因此  C1  可
以在  Data  之前收到  Invalidation。

我或S到M
缓存控制器向目录发送  GetM  请求并将块的状态从  I  更改为IMAD。 在此状态下,
缓存等待数
据和(可能) 指示其他缓存已使状态  S  中的块副本无效的  Inv‑Ack。
缓存控制器知道预期有多
少  Inv‑Ack,
因为数据消息包含AckCount,可能为零。

图8.3说明了目录响应  GetM  请求的三种常见情况。 如果目录处于状态  I, 它仅发送  AckCount  


为零的数据并进入状态  M。 如果处于状态  M, 目录控制器将请求转发给所有者并更新块的所
有者; 现在以前的所有者通过发送  AckCount  为零的数据来响应  Fwd‑GetM  请求。 最后一种
常见情况发生在目录处于状态  S  时。 目录以数据和等于共享者数量的  AckCount  响应, 加上
它向共享者列表中的每个核心发送  Invalidations。 接收到  Invalidation  消息的缓存控制器
使它们的共享副本失效, 并将  Inv‑Ack  发送给请求者。 当请求者收到最后一个  Inv‑Ack  时, 它
转换到状态  M。
注意表8.1中特殊的  Last‑Inv‑Ack  事件,它简化了协议规范。

这些常见情况忽略了一些可能的竞争, 这些竞争突出了目录协议的并发性。
例如,
核心  
C1  的缓存块处于  IMA  状态并接收到  Fwd‑GetS
Machine Translated by Google

8.2.基线目录系统  161

来自  C2  的缓存控制器。 这种情况是可能的, 因为目录已经向  C1  发送了数据, 向共享者发


送了  Invalidation  消息, 并将其状态更改为  M。 当  C2  的  GetS  到达目录时, 目录只是将
其转发给所有者  C1。 此  Fwd‑GetS  可能会在所有  Inv‑Ack  到达  C1  之前到达  C1。
在这种
情况下, 我们的协议只是停止, 缓存控制器等待  Inv‑Ack。 因为  Inv‑Acks  在一个单独的网
络上传输, 所以保证它们不会阻塞在未处理的  Fwd‑GetS  后面。

M  到  I
为了驱逐处于状态  M  的块, 缓存控制器发送一个包含数据的  PutM  请求并将块状态更
改为MIA。 当目录收到这个  PutM  时,
它更新  LLC/内存, 用  Put‑Ack  响应,
并转换到状态  I。
直到请求者收到  Put‑Ack, 块的状态保持有效  M, 缓存控制器必须响应转发块的一致性
请求。 在缓存控制器在发送  PutM  和接收  Put‑Ack  之间收到转发一致性请求(Fwd‑
GetS  或  Fwd‑GetM)的情况下,缓存控制器响应  Fwd‑GetS  或  Fwd‑GetM  并更改其块
分别向  SIA或IIA声明。 这些瞬态状态分别是有效的  S  和  I,但表示缓存控制器必须等待  
Put‑Ack  完成到  I  的转换。

S  到  
I  与前一章中的侦听协议不同, 我们的目录协议不会默默地逐出状态  S  中的块。 相反,为了
替换状态  S  中的块, 缓存控制器发送  PutS  请求并将块状态更改为  SIA 。 目录收到此  
PutS  并以  Put‑Ack  响应。
在请求者收到  Put‑Ack  之前,
块的状态实际上是  S。 如果缓存
控制器在发送  PutS  之后和收到  Put  Ack  之前收到  Invalidation  请求,
它将块的状态更
改为  IIA 。
这个瞬态实际上是  I, 但它表示缓存控制器必须等待  Put‑Ack  才能完成从  S  到  
I  的事务。

8.2.6  协议简化
该协议相对简单,
并牺牲了一些性能来实现这种简单性。
我们现在讨论两个简化:

‧  除了只有三个稳定状态之外,
最重要的简化是协议在某些情况下停止。 例如,当缓
存控制器在瞬态状态下接收到转发的请求时, 它会停止。
在第8.7.2节中讨论的更高
性能选项是处理消息并添加更多瞬态。

‧  第二个简化是目录发送数据(和  AckCount)
以响应将块状态从  S  更改为  M  的高
速缓存。 高速缓存已经具有有效数据, 因此目录只需发送一个无数据  AckCount。 我
们推迟添加这种新类型的消息, 直到我们在第  8.4  节中介绍  MOSI  协议。
Machine Translated by Google

162  8.目录一致性协议

8.3  添加独占状态
正如我们之前在侦听协议上下文中讨论的那样, 添加独占  (E)  状态是一项重要的优化,因为它使内
核能够读取并写入仅具有单个一致性事务的块, 而不是  MSI  协议所需的两个.在最高级别,此优
化与缓存一致性是使用侦听还是目录无关。 如果一个核心发出  GetS  并且该块当前未被其他核心
共享,
则请求者可以获得处于状态  E  的块。
该核心然后可以静默地将块的状态从  E  升级到  M  而
无需发出另一个一致性请求。

在本节中, 我们将  E  状态添加到我们的基线  MSI  目录协议中。 与上一章中的  MESI  侦听协


议一样, 协议的操作取决于  E  状态是否被视为所有权状态。 并且,
与  MESI  侦听协议一样, 主要的
操作差异涉及确定哪个一致性控制器应该响应目录在状态  E  中提供给缓存的块请求。 该块可能
已经从  E  静默升级到  M,因为目录将块交给状态  E  的缓存。

在拥有  E  块的协议中,解决方案很简单。  E(或  M) 中块的缓存是所有者, 因此必须响应请


求。发送到目录的一致性请求将被转发到状态  E  块的缓存。 因为  E  状态是所有权状态,所以  E  块
的驱逐不能静默执行; 缓存必须向目录发出  PutE  请求。
如果没有明确的  PutE, 该目录将不知道该
目录现在是所有者并且应该响应传入的一致性请求。 因为我们在本入门中假设  E  中的块是拥有
的,所以这个简单的解决方案就是我们在本节的  MESI  协议中实现的。

在不拥有  E  块的协议中, E  块可以被悄无声息地驱逐, 但协议的复杂性增加了。 考虑这样一


种情况, 核心  C1  在状态  E  中获得一个块, 然后目录从核心  C2  接收到  GetS  或  GetM。 目录知道  
C1  是  (i)  仍处于状态  E, (ii)  处于状态  M(如果  C1  进行了从  E  到  M  的静默升级的存储), 或  
(iii)  处于状态  I(如果协议允许  C1执行静默  PutE)。 如果  C1  在  M  中,
目录必须将请求转发给  
C1, 以便  C1  可以提供最新版本的数据。 如果  C1  在  E  中, C1  或目录可能会响应, 因为它们都具有
相同的数据。 如果  C1  在  I  中, 目录必须响应。 我们在第  8.8.1节中关于  SGI  起源[10]的案例研究中
更详细地描述了一种解决方案, 即让  C1  和目录都响应。 另一种解决方案是让目录将请求转发到  
C1。 如果C1在I中, C1通知目录响应C2; 否则, C1  响应  C2  并通知目录它不需要响应  C2。

8.3.1  高级协议规范
我们在图  8.6  中指定了事务的高级视图,
突出显示了与  MSI  协议的不同之处。
只有两个显着差异。
首先,
我有一个转变
Machine Translated by Google

8.3.添加独占状态  163

(1)  获取 (1)  获取 (2)  Fwd‑GetS (1)  获取

你 所有者 你
要求 要求 你 要求
中号→小号
我→小号 S→S 我→小号 中号→小号 我→E 我→E
E→S
(3)  资料

(2)  资料 (2)  资料
(3)  资料
从  I  到  S  的过渡 从  I  到  E  的过渡

(1)  得到M (1)  得到M (2)  Fwd‑GetM

你 所有者
要求 你 要求 米→米 米→我
我→男 我→男 我→男
E→M E→I

(2)  数据[ack=0]
(3)  数据[ack=0]

(3)  Inv‑Ack
(1)  得到M (2)  投资
分享者
要求 S→I 唯一的共享者可能是请求者, 在这种情况

我→男 下, 不会发送  Invalidation  消息, 并且从  Dir  
小号→中号
小号→中号 到  Req  的  Data  消息的  AckCount  为零。
(2)  投资 分享者
S→I
(2)  数据[ack>0]

(3)  Inv‑Ack
从  I  或  S  到  M  的过渡。
从  E  到  M  的过渡是无声的

(1)
PutM+数据 (1)  PutE(无日期) (1)  看跌期权


要求 你 要求 你 要求 S→I
米→我 米→我 E→I E→I S→I
S→S

(2)  Put‑Ack (2)  Put‑Ack (2)  Put‑Ack


从  M  或  E  或  S  到  I  的过渡

图  8.6:  MESI  目录协议的高级描述。
在每次转换中,
请求事务的缓存控制器表示为“Req”。
Machine Translated by Google

164  8.目录一致性协议

如果目录接收到状态  I  中的块的  GetS, 则可能发生到  E。
其次,存在用于驱逐状态  E  中的块的  
PutE  事务。 因为  E  是所有权状态,
所以不能静默地驱逐  E  块。
与状态  M  的块不同,
E  块是干净的,
因此  PutE  不需要携带数据; 该目录已经有该块的最新副本。

8.3.2  详细协议规范
在表8.3和8.4  中,
我们给出了  MESI  协议的详细规范,
包括瞬态。
与  MSI  协议的差异以粗体突出
显示。该协议将稳定  E  状态和瞬态状态添加到缓存状态集, 以处理最初处于状态  E  的块的事务。

该协议比  MSI  协议稍微复杂一些, 目录控制器增加了很多复杂性。 除了拥有更多状态之外,


目录控制器还必须区分更多可能的事件。 例如,
当一个  PutS  到达时,
目录必须区分这是否是“最
后一个”PutS;
也就是说, 这个  PutS  是否来自唯一的当前共享者? 如果此  PutS  是最后一个  PutS,
则目录的状态更改为  I。

8.4  添加拥有状态

出于同样的原因, 我们在第  7  章中将  Owned  状态添加到基线  MSI  侦听协议中, 架构师可能希


望将  Owned  状态添加到第8.2  节中介绍的基线  MSI  目录协议中。 回顾第2章,
如果缓存有一个
处于  Owned  状态的块, 那么该块是有效的、 只读的、 脏的(即它最终必须更新内存) 和拥有的(即
缓存必须响应一致性请求块)。 与  MSI  相比, 添加  Owned  状态在三个重要方面改变了协议: (1)  
在  M  中有一个块的缓存观察  Fwd‑GetS  将其状态更改为  O  并且不需要(立即) 将数据复制回
来到  LLC/内存, (2)  缓存(在  O  状态) 比  LLC/内存满足更多的一致性请求, 以及  (3)  有更多的  3  
跳事务(LLC/内存可以满足) 在  MSI  协议中)。

8.4.1  高级协议规范
我们在图  8.7  中指定了事务的高级视图, 突出显示了与  MSI  协议的不同之处。 最有趣的区别是当
块在所有者缓存中处于  O  状态而在一个或多个共享缓存中处于  S  状态时, 处于状态  I  或  S  的块
的请求者向目录发送  GetM  的事务。
在这种情况下, 目录将  GetM  转发给所有者, 并附加  
AckCount。
该目录还向每个共享者发送失效。 所有者收到  Fwd‑GetM  并用  Data  和  AckCount  
响应请求者。 请求者使用这个收到的  AckCount  来确定它何时收到最后一个  Inv‑Ack。 如果  
GetM  的请求者是所有者(在
Machine Translated by Google

8.4.添加拥有的状态  165

表  8.3:  MESI  目录协议 缓存控制器


加 铺
店 品

替 Fwd‑
GetS GetM
Fwd‑ 资
投 认
确 Dir  





来据   (ack=0)
Dir  




来      (ack>0)
Dir  




来      自


数 者

所 答
应 认




我 将  GetS  发送 发送
到  Dir/ISD 得到M到
目录/IMAD
ISD失速 失速 失速 失速 ‑/E  ‑/S ‑/S
IMAD摊位 失速 失速 失速 失速 ‑/M  ‑/IMA  ‑/M  ack‑‑  ack‑‑  ‑/M
有个摊位 失速 失速 失速 失速
小号 打 发送 将  PutS  发送 发送发票
得到M到 到  Dir/SIA 确认
目录/SMAD 要求/我
SMAD命中 失速 失速 失速 失速 发送 ‑/M  ‑/SMA 确认‑‑
应答

要求/
IMAD

SMA命中 失速 失速 失速 失速 确认‑‑  ‑/M
M命中 打 发送PutM 发送数据 发送数据
+数据到 到  Req  和 到  Req/I  
总监/MIA 目录/S
和 打 命中率/M 发送  PutE 发送数据 发送数据

(无数据) 到  Req  和到  Req/I
到  Dir/ 目录/S
就是这个

米娅摊位 失速 失速 发送数据 发送数据 ‑/我

Req  和 到  Req/IIA到  
总监/新航
这个摊位 失速 失速 发送数据 发送数据 ‑/我

请求和请求/
总监/新航 国际投资协会

新航摊位 失速 失速 发送发票 ‑/我

确认

请求/IIA
国际投资协会
失速 失速 失速 ‑/我
Machine Translated by Google

166  8.目录一致性协议

表  8.4:  MESI  目录协议 目录控制器



S 到

M 特
推 后


不 后


放 据+数

M 者



来 从

M 者


非 自)
PutE
(无


数 者

所 E从
把 者


非 据

我发送前 发送数据 发送看跌期权 发送看跌期权 发送看跌期权 发送看跌期权

独占数据 要求,
设置 确认请求 确认请求 确认请求 确认请求
要求,
设置 业主给
业主给 要求/米
要求/E
S发送数据 发送数据 消除 消除 消除 消除

到  Req, 请求,
发送 要求来自 要求来自 要求来自 要求来自
将  Req  添加到 投资分享 分享者,
送 分享者,
送 分享者,
送 分享者,

分享者 呃,
清楚 Put Put Put Put
分享者,
集合 确认请求 确认 确认请求 确认请求
业主给 要求/我

要求/米
E前锋 向前 发送看跌期权 发送看跌期权 复制数据到 发送看跌期权 发送看跌期权 发送看跌期权

去 得到M到 确认请求 确认请求 mem,


发送 确认请求 确认请求, 确认请求清除
楼主,
做 所有者,
集合 Put
业主给 确认请求,
清除 业主/我
所有者 要求/米
sharer,   业主/我

添加  Req  
到  Shar
呃,
清楚
业主/SD
M前锋 向前 发送看跌期权 发送看跌期权 复制数据到 发送看跌期权 发送看跌期权

去 得到M到 确认请求 确认请求 mem,


发送 确认请求 确认请求
楼主,
做 所有者,
集合 Put
业主给 确认请求,
所有者 要求 清除
分享者,
添加 业主/我

要求
分享者,
清晰的所有者
/标清

小号
失速 失速 消除 消除 消除 消除 将数据复制
要求来自 要求来自 要求来自 要求来自 到  LLC/
分享者,
送 分享者,
送 分享者,
送 分享者,
送 内存/秒
Put Put Put Put

确认请求 确认请求 确认请求 确认请求


Machine Translated by Google

8.4.添加拥有的状态  167

(1)  获取 (1)  获取 (2)  Fwd‑GetS

你 你 所有者
要求 我→小号 要求 M→O M→O
我→小号 我→小号
S→S O→O O→O

(2)  资料
(3)  资料
从  I  到  S  的过渡

(1)  得到M (1)  得到M (2)  Fwd‑GetM

要求 你 要求 你 所有者
我→男 我→男 我→男 米→米 米→我

(2)  数据[ack=0]
(3)  数据[ack=0]
(3)  数据[ack>=0]
(3)  Inv‑Ack
(2)  Fwd‑GetM[ack>=0]
(1)  GetM  (2)  Inv (1)  得到M
分享者 所有者
要求 S→I 要求 O→I
你 你
我→男 我→男
小号→中号 O→M
小号→中号 小号→中号

(2)  投资 分享者 (2)  投资 分享者


S→I S→I
(2)  数据[ack>0]
(3)  Inv‑Ack
(3)  Inv‑Ack
从  I  或  S  到  M  的过渡
(1)  看跌期权

(3)  Inv‑Ack

(1)  得到M 要求 S→I
分享者 (1)  PutO+日期 S→I
S→S
(2)  投资 S→I
你 你
要求
O→M O→M 要求 O→I (2)  Put‑Ack
O→I
O→S
(2)  投资 分享者 (1)
PutM+数据
(2)  确认计数 S→I
(2)  Put‑Ack 你
要求
(3)  Inv‑Ack 米→我 米→我

从  O  到  M  的过渡
(2)  Put‑Ack
从  M  或  O  或  S  到  I  的过渡

图  8.7:  MOSI  目录协议的高级描述。
在每次转换中,
请求事务的缓存控制器表示为“Req”。
Machine Translated by Google

168  8.目录一致性协议

状态  O)。
这里的区别在于目录直接将AckCount  发送给请求者,
因为请求者是所有者。

该协议有一个与  PutM  交易几乎相同的  PutO  交易。
它包含数据的原因与  PutM  事务包
含数据的原因相同,即因为  M  和  O  都是脏状态。

8.4.2  详细协议规范
表8.5和8.6给出了  MOSI  协议的详细规范,包括瞬态。 与  MSI  协议的差异以粗体突出显示。 该协
议将稳定的  O  状态以及瞬态OMAC、  OMA和  OIA状态添加到缓存状态集, 以处理最初处于状
态  O  的块的事务。 状态OMAC表示缓存正在等待  Inv‑Acks( A)  来自缓存和来自目录的  
AckCount  (C),
但不是数据。 因为这个块是从状态  O  开始的, 所以它已经有了有效数据。

当核心  C1  的缓存控制器在OMAC或SMAD中有一个块并从核心  C2  接收到该块的  Fwd‑
GetM  或  Invalidation  时,
会出现一个有趣的情况。  C2  的  GetM  必须在  C1  的  GetM  之前的
目录中被排序, 才会出现这种情况。 因此, 在观察  C1  的  GetM  之前,目录状态变为  M(由  C2  拥
有)。 当  C2  的  Fwd‑GetM  或  Invalidation  到达  C1  时,
C1  必须知道  C2  的  GetM  先被命令。
因此, C1  的缓存状态从OMAC或SMAD变为IMAD。 来自  C2  的转发  GetM  或  Invalidation  使  
C1  的缓存块无效, 现在  C1  必须等待  Data  和  Inv‑Acks。

8.5  表示目录状态

在前面的部分中,我们假定了一个完整的目录;也就是说,
目录维护每个块的完整状态,
包括(可
能)
具有共享副本的完整缓存集。
然而,这种假设与目录协议的主要动机相矛盾:可伸缩性。
在具有大量高速缓存(即块的大量潜
在共享器)的系统中,为每个块维护完整的共享器集需要大量存储,
即使在使用紧凑的位向量表
示时也是如此。对于缓存数量不多的系统,维护这个完整的集合可能是合理的,
但大型系统的架
构师可能希望使用更具可扩展性的解决方案来维护目录状态。

有很多方法可以减少目录为每个块维护的状态量。
这里我们讨论两个重要的技术:粗目录和有限指针。
我们独立讨论这些技术, 但观察到它们可以
结合使用。我们将每个解决方案与基线设计进行对比,如图  8.8  的顶部条目所示。

8.5.1  粗略目录
拥有完整的共享者集使目录能够将失效消息准确地发送到那些具有处于状态  S  的块的缓存控
制器。
减少目录状态的一种方法是
Machine Translated by Google

8.5.代表目录状态  169

表  8.5:  MOSI  目录协议 缓存控制器


加 铺
店 品

替 Fwd‑
GetS GetM
Fwd‑ 资
投 认
确 (ack=0)
Dir  




来      (ack>0)
Dir  




来      自


数 者

所 自




确 你 答
应 认





发送GetS 发送
到目录/ 得到M到
新闻处 目录/IMAD
ISD失速 失速 失速 失速 ‑/S ‑/S
IMAD摊位 失速 失速 失速 失速 ‑/M  ‑/有‑/M ack‑‑  
有个摊位 失速 失速 失速 失速 ack‑‑  ‑/M
小号 打 发送 发送  PutS 发送
得到M 到目录/ 应答
到目录/ 是 要求/我
SMAD
SMAD命中 失速 失速 失速 失速 发送 ‑/M  ‑/SMA 确认‑‑
应答

要求/
IMAD
SMA命中 失速 失速 失速 失速 确认‑‑  ‑/M
M命中 打 发送 发送数据 发送数据
普特姆 要求/O 要求/我
+数据到
总监/MIA
米娅摊位 失速 失速 发送数据 发送数据 ‑/我

要求/ 要求/IIA
他是
O命中 发送 发送 发送数据 发送数据
得到M 放 要求 要求/我
到目录/ +数据到
奥玛克 主任/内审办
OMAC命中 失速 失速 发送数据 发送数据 ‑/OMA唉‑‑

要求 要求/
IMAD
自己命中 失速 失速 发送数据 失速 确认‑‑  ‑/M

要求
内审摊位 失速 失速 发送数据 发送数据 ‑/我

要求 要求/
国际投资协会

新航摊位 失速 失速 发送 ‑/我

应答

要求/
国际投资协会

国际投资协会 失速 失速 失速 ‑/我
Machine Translated by Google

170  8.目录一致性协议

表  8.6:  MOSI  目录协议 目录控制器



S 从 者

所 从 者


非 特
推 后


不 后


放 据+数

M 者



来 据+数

M 从 者


非 PutO+日
期 者



来 PutO+日
期 从 者


我发送数据 发送数据 发送看跌期权 发送看跌期权 发送看跌期权 发送看跌期权

到  Req, 要求,
设置 确认请求 确认请求 确认请求 确认请求
将  Req  添加到 业主给
分享者/S 要求/米
S发送数据 发送数据 消除 消除 消除 消除

到  Req, 到  Req, 要求来自 要求来自 要求来自 要求来自


将  Req  添加到 发送  Inv  到 分享者,
送 分享者,
送 分享者,
送 分享者,

分享者 分享者,
集合 Put Put Put Put
业主给 确认请求 确认 确认请求 确认请求
要求,
清除 要求/我
共享者/M
O前进 发送确认 向前 消除 消除 消除 消除 复制数据到 消除
去 数到 得到M到 要求来自 要求来自 要求来自 要求来自 mem,
发送 要求来自
所有者, 请求,发送 所有者, 分享者,
送 分享者,
送 分享者,
复 分享者,
送 Put 分享者,

将  Req  添加到 投资分享 将  Inv  发送到 Put Put 制数据到内存, Put 确认请求,
清除 Put
分享者 呃,
清楚 分享者,
集合 确认请求 确认请求 发送 确认请求 自己的 确认请求
共享者/M 业主给 Put‑Ack  到 是/秒

要求,
清除 要求,
清除
分享者,
发 拥有者
送Ack
数到

要求/米
M前锋 向前 发送看跌期权 发送看跌期权 复制数据到 发送看跌期权 发送看跌期权

去 得到M到 确认请求 确认请求 mem,


发送 确认请求 确认请求
所有者, 所有者,
集合 Put

将  Req  添加到 业主给 确认请求,


清除
分享者/O 要求 自己的
你是

保守地维护一个粗略的共享者列表, 它是实际共享者集的超集。
也就是说, 共享者列表中的给定条目对应于一组K  个缓存, 如图8.8的中间条目所示。
如果该
组中的一个或多个缓存(可能) 具有处于状态  S  的块,
则共享者列表中的该位被设置。  
GetM  将导致目录控制器向该集合中的所有K高速缓存发送无效。 因此,
粗略目录以用于不
必要的无效消息的额外互连网络带宽以及用于处理这些额外无效消息的高速缓存控制器
带宽为代价来减少目录状态。
Machine Translated by Google

8.5.代表目录状态  171

2  位Log2C  位 C位

完整目录 共享列表
国家所有者 完整的共享者列表 中的每一位代表一个
(one‑hot  bit  vector)
缓存

2  位Log2C  位 C/K位

粗共享器列表(one‑ 粗略目录 共享者列表中的每一位代表  


国家所有者
hot  bit  vector) K  个缓存

2  位Log2C  位 i*log2C  位

国家所有者 指向共享者的指针 Limited  Directory 共享者列表分为i个条目,


每个条目是一个指向缓存的指针

图  8.8:
表示具有  C  节点的系统中块的目录状态。

8.5.2  有限指针目录
在具有C  个高速缓存的芯片中, 完整的共享者列表需要C个条目, 每个条目一位, 总共C位。 然而,
研究表明, 许多区块有零个共享者或一个共享者。 有限指针目录通过具有i  (i<C)个条目来利用此
观察结果, 其中每个条目需要log2C位,
总共i  *  log2C位,
如图  8.8  底部条目所示。 有限的指针目
录需要一些额外的机制来处理(希望不常见)
过充分研究的选项来处理这些情况,
系统尝试添加第i+1  个共享者的情况。
使用符号  DiriX  
有三个经
[2,  8]  表
示,
其中i指的是指向共享者的指针的数量,  X指的是处理系统尝试添加的情况的机制第i+1  个
分享者。

‧  广播(DiriB): 如果已经有i  个共享者并且另一个  GetS  到达,
目录控制器设置块的状态
以指示后续的  GetM  需要目录向所有缓存广播无效(即, 一个新的“太多共享者” 状
态)。  DiriB  的缺点是目录可能必须广播到所有C缓存, 即使只有K  个共享器(i<K<C),
需要目录控制器发送(缓存控制器处理)  CK不必要的无效消息.极限情况Dir0B通过消
除所有指针并要求对所有一致性操作进行广播, 将这种方法发挥到极致。 最初的  Dir0B提
议为每个块保留两个状态位, 编码三个  MSI  状态加上一个特殊的“单一共享器” 状态[3]。
当缓存尝试将其  S  副本升级为  M  副本时, 这种新状态有助于消除广播(类似于
Machine Translated by Google

172  8.目录一致性协议

独家状态优化)。 类似地, 目录的  I  状态在内存拥有该块时消除了广播。  AMD  的  Coherent  


HyperTransport  [6]实现了一个不使用目录状态的  Dir0B  版本,
放弃了这些优化但消除了存储
任何目录状态的需要。 发送到目录的所有请求然后被广播到所有缓存。

‧  无广播(DiriNB):
如果已经有i个共享者并且另一个GetS  到达,
则目录要求当前共享者之一
使自己无效, 以便在共享者列表中为新请求者腾出空间。 由于使共享者无效所花费的时间, 该解
决方案会对广泛共享的块(即, 由超过i  个节点共享的块)产生显着的性能损失。  DiriNB对于
具有连贯指令缓存的系统来说尤其成问题, 因为代码经常被广泛共享。

‧  软件(DiriSW ):
如果已经有i  个共享者并且另一个GetS  到达,
则系统陷入软件处理程序。

获到软件可实现极大的灵活性, 例如在软件管理的数据结构中维护完整的共享者列表。 然而,

于捕获到软件会导致显着的性能成本和实施复杂性, 因此这种方法在商业上的接受度有限。

8.6  目录组织
从逻辑上讲, 该目录包含每个内存块的单个条目。
许多传统的基于目录的系统,
其中目录控制器与内存
控制器集成在一起, 通过增加内存来保存目录来直接实现这种逻辑抽象。
例如,
SGI  Origin  添加了额外
的  DRAM  芯片来存储每个内存块的完整目录状态[10]。

然而,
对于当今的多核处理器和大型  LLC, 传统的目录设计已毫无意义。 首先,
架构师不希望目
录访问片外内存的延迟和功率开销, 尤其是对于缓存在芯片上的数据。 其次,
当几乎所有内存块在任何
给定时间都没有被缓存时, 系统设计人员对大型片外目录状态犹豫不决。 这些缺点促使架构师通过仅
在芯片上缓存目录条目的子集来优化常见情况。 在本节的其余部分,
我们将讨论目录缓存设计, 其中一
些设计先前已由  Marty  和  Hill  [13]  分类。

与传统的指令和数据缓存一样,目录缓存[7]提供对完整目录状态的子集的更快访问。因为目录
总结了连贯高速缓存的状态,所以它们表现出类似于指令和数据访问的局部性, 但只需要存储每个块
的连贯状态而不是其数据。因此,相对较小的目录缓存可实现较高的命中率。 目录缓存对一致性协议的
功能没有影响;它只是减少了平均目录访问延迟。在多核处理器时代, 目录缓存变得更加重要。在内核
位于单独的芯片和/或板上的旧系统中,消息延迟足够长, 以至于它们倾向于分摊
Machine Translated by Google

8.6.目录组织  173

目录访问延迟。在多核处理器中,
消息可以在少数几个周期内从一个内核传输到另一个内
核,而片外目录访问的延迟往往会使通信延迟相形见绌并成为瓶颈。
因此,
对于多核处理
器,有强烈的动机来实现片上目录缓存以避免昂贵的片外访问。

片上目录缓存包含完整目录条目集的一个子集。
因此,
关键的设计问题是处理目录缓存未命中,即,当针对其目录条目不在目录缓存中的块
的一致性请求到达时。
我们在表8.7中总结了设计选项,
接下来将对其进行描述。

表  8.7:
比较目录缓存设计

包含目录缓存(第8.6.2  节)

DRAM  目录 空目
支持的目录缓存(第 嵌入在  Inclusive  LLC  中的  
Inclusive  Directory  Cache(第 独立包含目录缓存(第8.6.2  节) 录缓存(第
8.6.1  节) 8.6.3  节)
8.6.2  节)

无召回 有召回  没有召回 随着召回

目录位置 内存 有限责任公司 有限责任公司 没有任何

使用  DRAM  是 不 不 不

直接小姐 必须访问 块必须是我 块必须是我 块可能是


保守党暗示 内存 在任何状态→必
须广播

收录要求 没有任何 LLC包括L1 目录缓存包括  L1s  无

执行 DRAM  加上独立 更大的  LLC   更大的  LLC  块 冗余标签的高度关 冗余标签的存 没有任何

费用 的片上缓存 块;
高度关联的 联存储 储
有限责任公司

更换通知 没有任何 没有任何 可取的 必需的 可取的 没有任何

8.6.1  DRAM  支持的目录缓存

最直接的设计是将完整的目录保存在  DRAM  中,就像在传统的多芯片多处理器中一样,
并使用单独的目录缓存结构来减少平均访问延迟。 在此目录缓存中未命中的一致性请求
会导致访问此  DRAM  目录。
这种设计虽然简单,但有几个重要的缺点。

首先,
它需要大量的  DRAM  来保存目录,包括当前未缓存在芯片上的绝大多数块的状态。
其次,
由于目录缓存与  LLC  解耦,
因此有可能命中  LLC  但未命中目录缓存,
因此即使数据
在本地可用,
也会导致  DRAM  访问。最后,目录
Machine Translated by Google

174  8.目录一致性协议

缓存替换必须将目录条目写回  DRAM,
从而导致高延迟和电源开销。

8.6.2  包含目录缓存
我们可以通过利用观察结果来设计更具成本效益的目录缓存, 即我们只需要为缓存在芯片上的
块缓存目录状态。如果目录缓存包含缓存在芯片上的所有块的超集的目录条目, 我们将目录缓存
称为包含目录缓存。包容性目录缓存作为一个“完美” 的目录缓存,在访问缓存在芯片上的块时
绝不会错过。不需要在  DRAM  中存储完整的目录。
包含目录缓存中的未命中表示该块处于状态  
I;
未命中并不是访问某些后备目录存储的先兆。

我们现在讨论两种包容性目录缓存设计,
以及适用于两种设计的优化。

嵌入在包容性  LLC  中的包容性目录缓存最简单的目录
缓存设计依赖于与上层缓存保持包含关系的  LLC 。 缓存包含意味着如果一个块在上层缓存
中,
那么它也必须存在于下层缓存中。 对于图8.1的系统模型,  LLC  包含意味着如果一个块在
内核的  L1  缓存中,
那么它也必须在  LLC  中。

LLC  包含的一个结果是,
如果一个块不在LLC中, 它也不在L1  缓存中, 因此对于芯片上的
所有缓存都必须处于状态  I。包容性目录缓存通过在  LLC  中嵌入每个块的一致性状态来利用此
属性。
如果一致性请求被发送到  LLC/目录控制器,并且请求的地址不存在于  LLC  中, 那么目录
控制器知道请求的块没有缓存在芯片上, 因此在所有  L1  中都处于状态  I。

因为目录镜像了  LLC  的内容, 所以只需向  LLC  中的每个块添加额外的位,就可以将整个
目录缓存嵌入到  LLC  中。
这些添加的位可能会导致不小的开销, 具体取决于内核的数量和目录
状态的表示格式。 我们在图  8.9  中说明了将此目录状态添加到  LLC  缓存块,并将其与没有  LLC  
嵌入式目录缓存的系统中的  LLC  块进行比较。

不幸的是,包含  LLC  有几个重要的缺点。 首先,
虽然  LLC  包含可以为私有缓存层次结构
自动维护(如果较低级别的缓存具有足够的关联性[4]), 对于我们系统模型中的共享缓存, 通
常需要发送特殊的“召回” 请求到在替换  LLC  中的块时使  L1  缓存中的块无效(在本节后面讨
论)。更重要的是,LLC  包含需要维护上层缓存中缓存块的冗余副本。 在多核处理器中,
上层缓
存的总容量可能是  LLC  容量的很大一部分(有时甚至大于)。
Machine Translated by Google

8.6.目录组织  175

标签 数据

(a)  典型的  LLC  块

目录
标签 数据
状态

(b)  带有  LLC  嵌入式目录缓存的  LLC  块

图  8.9:
实施  LLC  嵌入式目录缓存的成本。

独立的包容性目录缓存我们现在提
出了一种不依赖于  LLC  包含的包容性目录缓存设计。 在此设计中, 目录缓存是一个独立的
结构,在逻辑上与目录控制器相关联, 而不是嵌入到  LLC  本身中。 为了使目录缓存具有包
容性,它必须包含所有  L1  缓存中块联合的目录条目, 因为  LLC  中但不在任何  L1  缓存中
的块必须处于状态  I。
因此, 在该设计中,目录缓存由所有  L1  缓存中标签的副本组成。 与之
前的设计相比,这种设计更加灵活, 因为不需要包含  LLC, 但它增加了重复标签的存储成
本。

这种包容性目录缓存有一些重要的实施成本。 最值得注意的是, 它需要高度关联的目


录缓存。  (如果我们将目录缓存嵌入到包容性  LLC  中, 那么  LLC  也必须是高度关联
的。)考虑具有  C  内核的芯片的情况, 每个内核都有一个  K  路集合关联  L1  缓存。
目录缓
存必须是  C*K  方式关联的以保存所有  L1  缓存标签, 不幸的是关联性随着核心数线性增
长。
我们在图  8.10  中说明了  K=2  时的这个问题。

核心0 核心一 核心C‑1


设置  0 设置  0 设置  0 设置  0 设置  0 设置  0 设置  0
方式0 方式一 方式0 方式一 方式0 方式一
设置  1 设置  1 设置  1 设置  1 设置  1 设置  1

方式0 方式一 方式0 方式一 方式0 方式一


. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .

集S‑1 集S‑1 集S‑1 集S‑1 集S‑1 集S‑1 集S‑1


方式0 方式一 方式0 方式一 方式0 方式一

图  8.10:
包含目录缓存结构(假设  2  路  L1  缓存)。
每个条目都是对应于列顶部核心的集
合和方式的标签。
Machine Translated by Google

176  8.目录一致性协议

包容性目录缓存设计也引入了一些复杂性, 以便使目录缓存保持最新。 当一个块从  L1  缓存中


被逐出时, 缓存控制器必须通过发出显式  PutS  请求通知目录缓存关于哪个块被替换(例如, 我们不
能使用静默逐出协议, 如第  8.7.4  节所述) .一种常见的优化是在  GetS  或  GetX  请求上搭载显式  
PutS。
由于索引位必须相同, 因此可以通过指定替换的方式来对  PutS  进行编码。

这有时被称为“替换提示”,
尽管通常它是必需的(而不是真正的“提示”)。

限制包含目录缓存的关联性为了克服先前实现中高度关联
目录缓存的成本, 我们提出了一种限制其关联性的技术。 我们不是为最坏情况(C*K  关联性)设计目
录缓存, 而是通过不允许最坏情况发生来限制关联性。 也就是说,我们将目录缓存设计为  A‑way  set  
associative,
其中  A<C*K,
并且我们不允许将映射到给定目录缓存集的多个  A  条目缓存在芯片上。 当
缓存控制器发出向其缓存添加块的一致性请求, 并且目录缓存中的相应集合已经充满有效条目时, 目
录控制器首先从所有缓存中逐出该集合中的一个块。 目录控制器通过向所有保持此块处于有效状态
的缓存发出“召回” 请求来执行此逐出, 并且缓存以确认响应。 一旦目录缓存中的条目已通过此  
Recall  释放,目录控制器便可以处理触发  Recall  的原始一致性请求。

Recalls  的使用克服了目录缓存中对高关联性的需求, 但如果不仔细设计, 它可能会导致性能


不佳。
如果目录缓存太小, 那么  Recalls  会很频繁,
性能也会受到影响。康威等。  [6]提出了一个经验
法则,
目录缓存应该至少覆盖它包含的聚合缓存的大小, 但它也可以更大以降低召回率。 此外,
为了避
免不必要的召回, 该方案最适用于状态  S  中的块的非静默驱逐。 通过静默驱逐,不必要的召回将被发
送到不再持有被召回块的缓存。

8.6.3  空目录缓存(没有后备存储)
成本最低的目录缓存是根本没有目录缓存。 回想一下, 目录状态有助于修剪要向其转发一致性请求的
一致性控制器集。 但是与粗略目录(第8.5.1  节)
一样,
如果这种修剪没有完全完成,协议仍然可以正
常工作, 但会发送不必要的消息,并且协议的效率会低于它应有的水平。 在极端情况下,Dir0B协议(第
8.5.2  节)不进行任何修剪,
在这种情况下, 它实际上根本不需要目录(或目录缓存)。 每当一致性请
求到达目录控制器时, 目录控制器只需将其转发给所有缓存(即, 广播转发的请求)。这个目录缓存
设计, 我们
Machine Translated by Google

8.7.性能和可扩展性优化  177

称为  Null  Directory  Cache,
可能看起来简单, 但它在中小型系统中很受欢迎, 因为它不会产生存储成
本。
如果没有目录状态, 人们可能会质疑目录控制器的用途, 但它有两个重要的作用。 首先,与本章中
的所有其他系统一样, 目录控制器负责  LLC;
更准确地说,它是一个  LLC/目录控制器。其次,目录控制器
作为协议中的一个排序点; 如果多个核心同时请求同一个块, 则请求在目录控制器中排序。 目录控制器
决定哪个请求先发生。

8.7  性能和可扩展性优化

在本节中,
我们将讨论几种优化以提高目录协议的性能和可扩展性。

8.7.1  分布式目录
到目前为止,
我们假设有一个目录附加到一个单一的整体  LLC。
这种设计显然有可能在这个共享的中央资源上造成性能瓶颈。 集中式瓶颈问题的典型、
通用解决方案
是分配资源。给定块的目录仍然固定在一个地方,
但不同的块可以有不同的目录。

在具有  N  个节点的较旧的多芯片多处理器中 每个节点由多个芯片组成, 包括处理器内核和内


存 每个节点通常具有与其关联的内存的  1/N  和相应的目录状态的  1/N。

我们在图  8.11  中说明了这样一个系统模型。 分配给节点的内存地址通常是静态的, 并且通常可


以使用简单的算术轻松计算。 例如,在具有  N  个目录的系统中,块  B  的目录条目可能在目录B  模N  中。
每个块都有一个主目录, 这是保存其内存和目录状态的目录。 因此, 我们最终得到一个系统, 其中有多个
独立目录管理不同块集的一致性。 与要求所有一致性流量通过单个中央资源相比, 拥有多个目录可提
供更大的一致性事务带宽。

重要的是,分发目录对一致性协议没有影响。
在当今具有大型  LLC  和目录缓存的多核处理器世界中, 分发目录的方法在逻辑上与传统的多
芯片多处理器相同。我们可以分发(存储) LLC  和目录缓存。
每个块都有  LLC  的主库及其关联的目录
缓存库。
Machine Translated by Google

178  8.目录一致性协议

节点 节点


目 录

核 记忆 核 记忆

缓存 目录 缓存 目录
缓存 缓存
控制器 控制器 控制器 控制器

互联网络

图  8.11:
具有分布式目录的多处理器系统模型。

8.7.2  非停顿目录协议
迄今为止提出的协议的一个性能限制是一致性控制器在几种情况下会停止。 特别是,
当缓
存控制器在某些瞬态状态(例如IMA) 中接收到对块的转发请求时, 它们会停止。在表8.8
和8.9  中,
我们展示了在这些情况下不会停止的基线  MSI  协议的变体。 例如,
当缓存控制
器有一个处于  IMA  状态的块并接收到  Fwd‑GetS  时, 它会处理请求并将块的状态更改
为IMAS。 该状态表示在缓存控制器的GetM事务完成后(即最后一个Inv‑Ack到达时),
缓存控制器会将块状态变为S。 此时, 缓存控制器还必须将块发送给请求者GetS  并转到
现在是所有者的目录。 通过不在  Fwd‑GetS  上停止,
缓存控制器可以通过继续处理其传
入队列中  Fwd‑GetS  后面的其他转发请求来提高性能。

非停顿协议的一个复杂之处在于, 当处于IMAS  状态时,  Fwd‑GetM  可能会到达。
缓存控制器没有停止, 而是处理该请求并将块的状态更改为IMASI  (在  I  中, 转到  M,

待  Inv‑Acks,
然后转到  S,
然后转到  I)。  SMA中的模块会出现一组类似的瞬态。 通常, 消
除停顿会导致更多的瞬态, 因为一致性控制器必须跟踪(使用新的瞬态) 它正在处理的附
加消息而不是停顿。

我们没有从目录控制器中删除摊位。
与第7  章中的侦听协议中的内存控制器一样,

们需要添加不切实际的大量瞬态,
以避免在所有可能的情况下出现停顿。
Machine Translated by Google

8.7.性能和可扩展性优化  179

表  8.8:
非停顿  MSI  目录协议 缓存控制器


加 铺
店 品

替 Fwd‑
GetS GetM
Fwd‑ 资
投 认
确 自


数 你 0)
=确
(认 自


数 你0)
(认
>确   自


数 者

所 答
应 认




我 发送 发送
去 得到M到
主任/ISD 目录/IMAD
新闻处 个子高 失速 失速 发送发票 ‑/S ‑/S
确认请求
/  ISDI

是我 高的 失速 失速 ‑/我 ‑/我
小号

IMAD摊位 失速 失速 失速 失速 ‑/M  ‑/IMA  ‑/M  ack‑‑  ack‑‑  ‑/M  ack‑‑  
有个摊位 失速D 失速 ‑/IMAS  ‑/IMAI 发送数据到
IMAS摊位 失速 失速 发送
应答
要求/ 要求和
服用 目录/S
摊位_ 失速 失速 ack‑‑发送数据

要求和
Dir/I  
今井摊位 失速 失速 ack‑‑发送数据

要求/我
小号 打 发送 将  PutS  发 发送发票
GetM   送到目录 确认
到  Dir/ /是 要求/我
SMAD
SMAD命中 失速 失速 失速 失速 发送发票 ‑/M  ‑/SMA 确认‑‑
确认请求/
IMAD
SMA命中 失速 失速 ‑/SMAS  ‑/SMAI ack‑‑  ‑/M  
SMAS摊位 失速 失速 发送 ack‑‑  发送数据
应答 到
要求/ 要求和
形状记忆合金 目录/S
SMASI摊位 失速 失速 ack‑‑发送数据

要求和
Dir/I  
SMAI摊位 失速 失速 ack‑‑发送数据到

要求/我
M命中 打 发送 发送数据 发送数据
PutM   到  Req  和到  Req/I
+数据到 目录/S
总监/MIA
米娅摊位 失速 失速 将数据发送 发送数据 ‑/我

和 到  Req/IIA到  Req  
总监/新航
是 失速 失速 失速 发送发票 ‑/我

确认请求/
国际投资协会

国际投资协会 失速 失速 失速 ‑/我
Machine Translated by Google

180  8.目录一致性协议

表  8.9:
非停顿  MSI  目录协议 目录控制器



S 从 者

所 特
推 后


不 后


放 据+数

M 者



来 据+数

M 从 者


非 据


发送数据至 发送数据至 发送确认 发送确认 发送确认

Req,
将  Req  添加到 请求,
设置所有者 要求 要求 要求
Sharer/S  到  Req/M
小号
发送数据至 发送数据至 发送确认 发送确认 从共享者中删除  
Req,
将  Req  添加到 Req,
将  Owner  设置为  Req,
删除 要求,
删除 Req,
发送  Put‑
分享者到  Req,
发送  Inv Req  from   来自分享者的请 Ack
to  Sharers,  清除 分享者 求/我 要求
共享者/M
M前向  GetS 转发GetM 发送确认 发送确认 复制数据 发送确认
到所有者,
添加到所有者,
设置 要求 要求 到内存, 发送   要求
要求分享者, 所有者要求清除 Put‑Ack
所有者/SD 要求,
清除
业主/我

小号 失速 失速 发送确认 发送确认 从共享者中删除   复制数据到内
要求,
删除 要求,
删除 Req,
发送  Put‑ 存/S
要求来自 要求来自 Ack
分享者 分享者 要求

8.7.3  没有点对点排序的互连网络

我们在8.2  节中提到,
在讨论我们的基线  MSI  目录协议的系统模型时,
我们假设互连网络为转发
请求网络提供点对点排序。 当时,我们声称点对点排序简化了架构师在设计协议时的工作, 因为排
序消除了某些特定的可能性。

比赛。

我们现在提供一个示例竞赛, 如果我们在互连网络中没有点对点或排序, 则该竞赛是可能


的。我们假设  8.4  节中的  MOSI  协议。  Core  C1  的缓存拥有一个处于状态  M  的块。
Core  C2  向
目录发送  GetS  请求,Core  C3  向目录发送  GetM  请求。 该目录先接收C2的GetS,再接收C3的
GetM。
对于这两个请求, 目录将它们转发到  C1。

‧  对于点对点排序(如图8.12  所示):  C1  接收  Fwd‑GetS,
响应数据,
并将块状态更改
为  O。
C1  然后接收  Fwd‑GetM,
响应数据,
并更改块状态块状态到  I。 这是预期的结果。

‧  没有点对点排序(如图8.13  所示):
来自  C3  的  Fwd‑GetM  可能首先到达  C1。  C1  向  
C3  响应数据并将块状态更改为  I。
Machine Translated by Google

8.7.性能和可扩展性优化  181

Fwd‑GetM
2个

(C3) 你

5个
得到M
1个

时间C1的状态
Fwd‑GetS 得到S

(C2)
1个
3个

C1 C2 C3

4个

4个 数据
6个 我
数据
6个

图  8.12:
点对点排序示例。

得到M
2个

(C3) 你
3个

得到M
1个

时间C1的状态
5个 得到S
得到S

1个 米 (C2)
C1 C2 C3

4个

4个

数据

图  8.13:
没有点对点排序的示例。
请注意,
C2  的  Fwd‑GetS  在状态  I  中到达  C1,
因此  C1  
不响应。

然后来自  C2  的  Fwd‑GetS  到达  C1。  C1  在  I  中,
无法响应。
来自  C2  的  GetS  请求
永远得不到满足, 系统最终会死锁。

到目前为止,我们介绍的目录协议与不为转发请求网络提供点对点顺序的互连网络
不兼容。
为了使协议兼容,我们必须修改它们以正确处理如上所述的比赛。消除此类竞争的
一种典型方法是添加额外的握手消息。在上面的示例中,
目录可以等待缓存控制器
Machine Translated by Google

182  8.目录一致性协议

在转发另一个请求之前确认收到发送给它的每个转发请求
给它。

鉴于点对点排序降低了复杂性,
这似乎是一个显而易见的设计决策。
但是,强制执行点
对点排序会阻止我们在互连网络中实施一些可能有用的优化。
值得注意的是,它禁止无限制
地使用自适应路由。

自适应路由使消息能够在穿过网络时动态选择其路径, 通常是为了避免拥塞的链路或
交换机。 自适应路由虽然对传播流量和缓解拥塞很有用, 但它使端点之间的消息能够采用不同
的路径, 从而以与发送消息时不同的顺序到达。
考虑图  8.14  中的示例,其中交换机  A  向交换
机  D  发送两条消息,
M1  和  M2。

使用自适应路由, 它们采用不同的路径, 如图所示。如果交换机  B  的拥塞比交换机  C  的拥塞多,


那么  M2  可能会先于  M1  到达交换机  D,
尽管它是在  M1  之后发送的。

留言M1

A 乙

C 丁

消息M2

图  8.14:
自适应路由示例。

8.7.4  静音  VS。
状态  S  中的块的非静默驱逐
我们设计了我们的基线目录协议, 这样缓存就不能悄悄地驱逐处于状态  S  的块(即, 不发出  
PutS  来通知目录)。要驱逐  S  块,
缓存必须向目录发送  PutS  并等待  Put‑Ack。
另一种选择是
允许静默驱逐  S  块。  (如果认为  E  状态不是所有权状态, 则可以对状态  E  中的块进行类似
的讨论, 在这种情况下, 可以静默驱逐  E  块。)
Machine Translated by Google

8.8.案例研究  183

静默  PutS  的优点显式  
PutS  的缺点是它使用互连网络带宽 尽管是针对小的无数据  PutS  和  Put‑Ack  消息
即使在它最终没有帮助的情况下也是如此。
例如, 如果核心  C1  向目录发送一个  PutS, 然后随后想要对该块执行加载, 则  C1  向目录发送一个  
GetS  并重新获取  S  中的块。
如果  C1  在任何干预的  GetM  请求之前发送这第二个  GetS从其他核心,
那么  PutS  交易没有任何意义, 但确实消耗了带宽。

显式  PutS  的优点发送  PutS  
的主要动机是  PutS  使目录能够从其共享者列表中删除不再共享块的缓存。 拥有更精确的共享者列
表有三个好处。 首先,当后续的  GetM  到达时,目录不需要向该缓存发送  Invalidation。  GetM  事务
通过消除  Invalidation  并必须等待后续的  Inv‑Ack  来加速。其次,在  MESI  协议中,
如果目录精确地
计算共享者, 它可以识别最后一个共享者驱逐其块的情况; 当目录知道没有共享者时, 它可以用独占数
据响应  GetS。 第三,
回顾第8.6.2节,使用  Recalls  的目录缓存可以受益于显式  PutS  消息以避免不必
要的  Recall  请求。

发送  PutS  的第二个动机,
以及我们的基准协议确实发送  PutS  的原因, 是它通过消除一些竞
争来简化协议。 值得注意的是, 在没有  PutS  的情况下,
静默地逐出  S  中的块然后发送  GetS  请求以
重新获取  S  中被逐出的块的缓存可以在接收其  GetS  的数据之前从目录接收到  Invalidation。

在这种情况下, 缓存不知道失效是属于它在  S  中保存块的第一个时期还是第二个时期(即,失效是
在  GetS  之前还是之后序列化)。这场比赛最简单的解决方案是悲观地假设最坏的情况(无效属于
第二个周期) 并且总是在数据到达时立即使块无效。 存在更有效的解决方案,但会使协议复杂化。

8.8  案例研究

在本节中, 我们将讨论几种商业目录一致性协议。 我们从传统的多芯片系统  SGI  Origin  2000  开始。


然后我们讨论最近开发的目录协议, 包括  AMD  的  Coherent  HyperTransport  和随后的  Hyper  
Transport  Assist。
最后,
我们介绍  Intel  的  QuickPath  Interconnect  (QPI)。

8.8.1  SGI  起源  2000
Silicon  Graphics  Origin  2000  [10]是一款商用多芯片多处理器,
设计于  20  世纪  90  年代中期,
可扩
展至  1024  个内核。 对可伸缩性的强调需要一个可伸缩的一致性协议, 从而产生了第一个使用目录的
商业共享内存系统
Machine Translated by Google

184  8.目录一致性协议

协议。  Origin  的目录协议从斯坦福  DASH  多处理器[11]的设计演变而来, 因为  DASH  和  Origin  有
重叠的架构团队。
如图8.15  所示,  Origin  包含多达  512  个节点,
其中每个节点包含两个  MIPS  R10000  处理
器,
它们通过总线连接到称为集线器的专用  ASIC。 与类似的设计不同, Origin  的处理器总线没有利用
相干侦听, 而是简单地将处理器相互连接并连接到节点的集线器。 集线器管理缓存一致性协议并将节
点连接到互连网络。 集线器还连接到分布式内存和目录的节点部分。 该网络不支持任何排序, 甚至不支
持节点之间的点对点排序。 因此, 如果处理器  A  向处理器  B  发送两条消息, 它们到达的顺序可能与它们
到达时的顺序不同

发送。

MIPS MIPS
10000  兰特 10000  兰特

节点 节点 节点

总线(不连贯)

中心 记忆  &
互联网络 目录
网络

图  8.15:  SGI  起源。

Origin  的目录协议有一些值得讨论的显着特征。
首先,
由于其可伸缩性, 每个目录条目包含的位数少于表示可能共享块的每个可能缓存所需的位数。

录为每个目录条目动态选择使用粗略的位向量表示或有限的指针表示(第8.5  节)。

该协议中第二个有趣的特性是, 由于网络不提供  or  dering,
因此可能存在几种新的一致性消
息竞争条件。值得注意的是,
第8.7.3节中的示例是可能的。 为了保持正确性, 协议必须考虑所有这些由
于不强制在网络中执行排序而引入的可能竞争条件。

第三个有趣的特征是协议使用非所有权  E  状态。 因为
E  状态不是所有权状态, 缓存可以静默地驱逐状态  E(或状态  S) 中的块。
Origin  的第四个有趣的特性是它提供了一个特殊的升级一致性请求以从  S  过渡到  M  而无需
不必要地请求数据, 这并不罕见但确实引入了一个新的种族。 在处理器  P1  发送升级和升级在目录中序
列化之间存在一个漏洞窗口; 如果另一个处理器的  GetM  或  Upgrade  先被序列化,
那么当它的  
Upgrade  到达目录时  P1  的状态是  I,
并且  P1
Machine Translated by Google

8.8.案例研究  185

事实上需要数据。
在这种情况下,
目录向  P1  发送否定确认  (NACK),
P1  必须向目录发送  GetM。

Origin  的  E  状态的另一个有趣特征是当处理器处于  E  时如何满足请求。 考虑处理器  P1  在状
态  E  中获得块的情况。 如果  P2  现在向目录发送  GetS,
目录必须考虑  P1  ( a)  可能已经悄悄地驱逐了
块, (b)  可能有一个未修改的块值(即, 与内存中的值相同), 或  (c)  可能有一个修改过的块值。 为了处
理所有这些可能性, 目录向  P2  响应数据,
并将请求转发给  P1。  P1  向  P2  发送新数据(如果在  M  中)
或只是一个确认。  P2  必须等待两个响应都到达才能知道要使用哪个消息的数据。

Origin  的另一个怪癖是它只使用两个网络(请求和响应)而不是避免死锁所需的三个网络。 目
录协议具有三种消息类型(请求、 转发请求和响应),因此名义上需要三个网络。相反,
Origin  协议会
检测何时可能发生死锁, 并向响应网络上的请求者发送“退避”消息。退避消息包含请求需要发送到的
节点列表, 然后请求者可以在请求网络上发送给它们。

8.8.2  相干超传输
目录协议最初是为了满足高度可扩展系统的需要而开发的, SGI  Origin  是此类系统的典型示例。 然而,
最近, 目录协议甚至对于中小型系统也变得很有吸引力, 因为它们有助于在互连网络中使用点对点链
接。目录协议的这一优势激发了  AMD  的  Coherent  HyperTransport  (HT)  [5]  的设计。  Coherent  
HT  支持将  AMD  处理器无缝连接到小型多处理器中。 也许具有讽刺意味的是, Coherent  HT  实际上
使用了广播, 从而表明目录协议在这种情况下的吸引力在于使用点对点链接, 而不是可扩展性。

AMD  观察到,
最多可以构建具有八个处理器芯片的系统, 每个芯片只有三个点对点链路, 芯片到
芯片的最大距离为三个链路。 八个处理器芯片, 每个处理器芯片有  6  个内核,意味着系统有  48  个内核。
为了保持协议简单, Coherent  HT  使用  Dir0B目录协议(第8.5.2  节)
的变体,该协议不存储稳定的目
录状态。发送到目录的任何一致性请求都将转发到所有缓存控制器(即广播)。  Coherent  HT  也可
以被认为是空目录缓存的一个例子: 请求总是在(空) 目录缓存中丢失, 所以它总是广播。
由于广播, 该
协议无法扩展到大型系统, 但这不是目标。

在采用  Coherent  HT  的系统中,每个处理器芯片都包含一定数量的内核、一个或多个集成内存
控制器、 一个或多个集成  HyperTransport  控制器,
以及一到三个与其他处理器芯片相连的  
Coherent  HT  链路。
一个“节点” 由一个处理器芯片及其相关的内存组成,它是它的家。
Machine Translated by Google

186  8.目录一致性协议

有许多可行的互连网络拓扑,如图8.16  所示的四节点系统。
值得注意的是,
该协议不要求一致
性请求的总顺序,这为互连网络提供了更大的灵活性。

内存 内存

核 核
多核 多核
处理器 处理器
相干
HT链接
横杆 忆



多核 多核
处理器 处理器

H  
T H  
T


控 制

内存 内存

图  8.16:
四节点相干  HyperTransport  系统(改编自[5])。

一致性事务的工作原理如下。 一个核心向主节点的目录控制器单播一个一致性请求, 就像在


一个典型的目录协议中一样。 由于目录没有状态,
因此无法确定哪些核心需要观察请求, 目录控制器
然后将转发的请求广播到所有核心, 包括请求者。  (这个广播就像在监听协议中发生的一样,除了
广播不是完全有序的并且不是由请求者发起的。) 然后每个核心接收转发的请求并向请求者发送响
应(数据或确认) .一旦请求者收到了所有响应,它就会向主节点的目录控制器发送一条消息以完成
事务。

查看此协议, 可以将其视为两全其美。 乐观地说,它具有没有目录状态或复杂性的点对点链接,


并且它的可扩展性足以支持多达八个处理器。 悲观地说,它有目录的长三跳延迟 或者四跳, 如果你
考虑从请求者到家庭的消息来完成交易, 尽管这个消息不在关键路径上 广播流量高窥探。 事实上,
相干  HT  使用的带宽甚至比侦听更多, 因为所有广播转发的请求都会生成响应。 相干  HT  的缺点激发
了一种称为  HyperTransport  Assist  [6]  的增强设计。
Machine Translated by Google

8.8.案例研究  187

8.8.3  超级运输辅助
对于代号为  Magny  Cours  的基于  12  核  AMD  Opteron  处理器的系统, AMD  开发了  
HyperTransport  Assist  [6]。  HT  Assist  无需广播每个转发的一致性请求, 从而增强了  Coherent  
HT。  HT  Assist不是Coherent  HT  的  Dir0B  类协议, 而是使用类似于第  8.6.2  节中描述的设计的目
录缓存。 每个多核处理器芯片都有一个包容性目录缓存, 每个块都有一个目录条目  (a)  它是它的主目
录和  (b)  缓存在系统中的任何地方。 没有  DRAM  目录, 因此保留了  Coherent  HT  的一项关键特性。 目
录缓存中的未命中表示该块未缓存在任何地方。  HT  Assist  的目录缓存使用  Recall  请求来处理目
录缓存已满并需要添加新条目的情况。 尽管到目前为止, 从我们的描述来看, HT  Assist  与第  8.6.2节中
的设计非常相似, 但它有几个我们更详细介绍的显着特征。

首先,
目录条目仅提供足够的信息来确定一致性请求是否必须转发到所有核心、转发到单个核
心或由主节点的内存满足。也就是说,
该目录没有维护足够的状态来区分需要将请求转发到两个核心
和需要将请求转发到所有核心。这个设计决策消除了必须维护每个块的共享者的确切数量的存储成
本;
相反,两个目录状态将“一个共享者”与“多个共享者”
区分开来。

其次, HT  Assist  设计谨慎,避免招致大量召回。
AMD  遵循一条经验法则, 即目录条目的数量至少应为缓存块的两倍。 有趣的是,AMD  选择不发送明确
的  PutS  请求;
他们的实验显然使他们相信, 额外的  PutS  流量不值得在减少召回方面获得有限的好
处。

第三,
目录缓存共享LLC。  LLC  在启动时由  BIOS  静态分区, 默认分配  1  MB  给目录缓存, 剩余  
5  MB  分配给  LLC  本身。
分配给目录缓存的  LLC  的每个  64  字节块被解释为  16  个  4  字节目录条目,
组织为四个  4  路集合关联集。

8.8.4  INTEL  QPI从  
2008  Intel  Core  微架构开始,  Intel  开发了用于连接处理器芯片的  QuickPath  Interconnect  
(QPI)  [9,  12] ,
QPI  首次出现在  Intel  Core  i7‑9xx  处理器中。 在此之前, 英特尔将处理器芯片与称为
前端总线  (FSB)  的共享总线相连。  FSB  从单个共享总线发展到多个总线, 但  FSB  方法从根本上受
到总线电气信号限制的瓶颈。 为了克服这个限制, Intel  设计了  QPI  以通过点对点(即非总线) 链路连
接处理器芯片。  QPI  指定了网络堆栈的多个级别, 从物理层到协议层。 出于本入门的目的, 我们在这
里重点关注协议层。

QPI支持五种稳定的相干态,典型的MESI态和F(orward)态。
F  状态是一个干净的只读状态, 它与  S  状态的区别在于缓存
Machine Translated by Google

188  8.目录一致性协议

具有  F  中的块的节点可以用数据(即转发数据) 响应一致性请求。在给定时间,
只有一个高速缓存可以
保存  F  中的一个块。  F状态与O状态有些相似,
不同的是F中的块不是脏的,因此可以被悄无声息地驱
逐;
希望驱逐  O  中的块的高速缓存必须将该块复制回内存。  F  状态的好处是它允许从缓存中获取只读
数据, 这通常比从内存中获取数据更快(内存通常在块为只读时响应请求)。

QPI  提供两种不同的协议模式,
具体取决于系统的大小:
“home  snoop”
和“source  snoop”。

QPI  的  Home  Snoop  模式是一种有效的可扩展目录协议(即, 不要被其名称中的“snoop”
一词混淆2 )。 与典型的目录协议一样, 核心  C1  向主节点  C2  的目录发出请求, 然后目录将该请求仅转
发给需要查看它的节点, 比如  C3(M  中的所有者)。  C3  向  C1  响应数据,并向  C2  发送一条消息以通
知目录。
当  C2  的目录收到来自  C3  的通知时, 它向  C1  发送“完成” 消息,此时  C1  可以使用它从  C3  收
到的数据。该目录作为协议中的序列化点和解析消息

比赛。

QPI  的  Source  Snoop  协议模式旨在实现低延迟一致性事务, 但代价是不能很好地扩展到具
有许多节点的大型系统。 核心  C1  向所有节点广播请求,包括家庭节点。 每个核心都以“窥探响应” 响应  
home,
表明该块在该核心处处于什么状态; 如果该块处于状态  M,
则除了对  home  的侦听响应之外, 核
心还将该块发送给请求者。 一旦家庭接收到请求的所有探听响应, 该请求就被订购了。 此时,home  要么
向请求者发送数据(如果没有核心拥有  M  中的块), 要么向请求者发送非数据消息; 当请求者收到任
何一条消息时, 都会完成交易。

Source  Snoop  对广播请求的使用类似于侦听协议, 但广播请求的关键区别不是在完全有序的
广播网络上传播。
因为网络不是完全有序的, 协议必须有解决竞争的机制(即, 当两个广播竞争时,核心  C1  在广播  B  之
前看到广播  A, 核心  C2  在广播  A  之前看到  B)。
这种机制由主节点提供,
尽管其方式不同于目录协议中
的典型竞争排序。 通常, 被请求块的主目录根据哪个请求先到达主目录来排序两个竞速请求。 相反, QPI  
的  Source  Snoop  会根据哪个请求的监听响应都已到达家中来对请求进行排序。

考虑竞争情况, 其中块  A  最初在  C1  和  C2  的缓存中处于状态  I。  C1  和  C2  都决定广播  A  的  
GetM  请求(即,
将  GetM  发送到另一个核心和主节点)。 当每个核收到另一个核的  GetM  时, 它向  
home  发送一个探听响应。 假设  C2  的监听响应在  C1  之前到达家中

2英特尔使用“snoop”
一词来指代内核在收到来自另一个节点的一致性请求时所做的事情。
Machine Translated by Google

8.9.目录协议的讨论和未来  189

窥探响应。 在这种情况下,
C1  的请求被排在第一位, home  向  C1  发送数据并通知  C1  有比赛。然后  
C1  向家庭发送确认,
家庭随后向  C1  发送一条消息,既完成  C1  的交易, 又告诉  C1  将块发送给  C2。
处理这种竞争比在典型的目录协议中复杂一些, 在典型的目录协议中, 请求在到达目录时被排序。

由于广播, Source  Snoop  模式比  Home  Snoop  使用更多带宽, 但  Source  Snoop  的常见


情况(无竞争) 事务延迟更小。  Source  Snoop  与  Coherent  HyperTransport  有点相似, 但有一
个关键区别。 在相干  HT  中, 请求被单播到家庭, 家庭广播请求。 在  Source  Snoop  中,请求者广播请
求。
因此, Source  Snoop  在解决竞争时引入了更多的复杂性, 因为没有可以对请求进行排序的单
点;  Coherent  HT  将  home  用于此目的。

8.9  目录的讨论和未来
协议
目录协议已经开始主导市场。
即使在小型系统中,目录协议也比侦听协议更常见,
主要是因为它们有助
于在互连网络中使用点对点链接。
此外,目录协议是需要可伸缩缓存一致性的系统的唯一选择。尽管
有许多优化和实施技巧可以减轻窥探的瓶颈,但从根本上说,
它们都不能消除这些瓶颈。对于需要扩
展到数百甚至数千个节点的系统,
目录协议是实现一致性的唯一可行选择。由于它们的可扩展性,我们
预计目录协议将在可预见的未来继续占据主导地位。

但是, 未来高度可扩展的系统有可能不连贯, 或者至少在整个系统中不连贯。 或许这样的系统


将被划分为连贯的子系统, 但是子系统之间并没有保持连贯性。或者这样的系统可能会效仿超级计算
机,
例如  Cray  的超级计算机,
这些系统要么没有提供一致性[14] ,
要么提供了一致性但限制了可以
缓存的数据[1]。

8.10  参考文献
[1]  D.  Abts、
S.  Scott  和  DJ  Lilja。
状态如此之多,
时间如此之少: 验证  Cray  X1  中的内存一致性。
在过程中。 国际并行和分布式处理研讨会,  2003.  DOI: 10.1109/ipdps.2003.1213087。  
189

[2]  A.  Agarwal、 R.  Simoni、
M.  Horowitz  和  J.  Hennessy。缓存一致性的目录方案评估。 在过程
中。 第  15  届年度国际计算机体系结构研讨会, 第  280–89  页,
1988  年  5  月。
DOI:
10.1109/
isca.1988.5238。  171
Machine Translated by Google

190  8.目录一致性协议

[3]  JK  阿奇博尔德和  J.‑L。 贝尔。缓存一致性问题的经济解决方案。
在过程中。 第  11  届计算机体系结构国际研讨会,第  355–62  页,
1984  年  6  月。
DOI:
10.1145/800015.808205。  171

[4]  J.‑L。
贝尔和  W.‑H。 王。
关于多级缓存层次结构的包含属性。
在过程中。 第  15  届计算机体系结构国际研讨会,
第  73‑80  页,
1988  年  5  月。
DOI:
10.1109/
isca.1988.5212。  174

[5]  P.  康威和  B.  休斯。  AMD  Opteron  北桥架构。  IEEE微,  27(2):10–21,
2007  年  3  月/4  月。
DOI: 10.1109/mm.2007.43。  185,  186

[6]  P.  Conway、 N.  Kalyanasundharam、 G.  Donley、 K.  Lepak  和  B.  Hughes。  AMD  Opteron  


处理器的高速缓存层次结构和内存子系统。  IEEE  Micro,  30(2):16–29, 2010  年  3  月/4  月。
DOI: 10.1109/mm.2010.31。  172,  176,  186,  187

[7]  A.  Gupta,  W.‑D.韦伯和  T.  Mowry。
减少可扩展的基于目录的缓存一致性方案的内存和流量需
求。 在过程中。 并行处理国际会议,  1990  年。 DOI:
10.1007/978‑1‑4615‑3604‑8_9。  172

[8]  MD  Hill、
JR  Larus、
SK  Reinhardt  和  DA  Wood。协作共享内存: 可扩展多处理器的软件和硬
件。  ACM  计算机系统交易,  11(4):300–18, 1993  年  11  月。
DOI:
10.1145/161541.161544。  
171

[9]  英特尔公司。
英特尔  QuickPath  互连简介。
文档编号  320412–001US,
2009  年  1  月。
187

[10]  J.  Laudon  和  D.  Lenoski。  SGI  起源: 一个  ccNUMA  高度可扩展的服务器。 在过程中。


第  24  
届年度国际计算机体系结构研讨会, 第  241‑51  页,
1997  年  6  月。
DOI: 10.1145/264107.264206。  162,  172,  183

[11]  D.  Lenoski,  J.  Laudon,  K.  Gharachorloo,  W.‑D.韦伯、
A.  古普塔、
J.  轩尼诗、
M。
霍洛维茨和  M.  Lam。 斯坦福  DASH  多处理器。  IEEE  计算机,  25(3):63–79, 1992  年  3  月。
DOI: 10.1109/2.121510。  184

[12]  RA  Maddox、
G.  Singh  和  RJ  Safranek。
编织高性能多处理器结构:
对英特尔  QuickPath  互连
的架构洞察。 英特尔出版社, 2009.  187

[13]  马蒂先生和希尔博士。 支持服务器整合的虚拟层次结构。 在过程中。 第  34  届年度计算机体系结


构国际研讨会,  2007  年  6  月。
DOI:  10.1145/1250662.1250670。  172

[14]  SL  斯科特。  Cray  T3E  多处理器中的同步和通信。 在过程中。
第七届编程语言和操作系统架构
支持国际会议, 第  26‑36  页,1996  年  10  月。
189
Machine Translated by Google

191

第九章

连贯性高级主题
在第  7  章和第8  章中,
我们在最简单的系统模型的上下文中介绍了监听和目录一致性协议, 这些
模型足以解释这些协议的基本问题。 在本章中, 我们在几个方向上扩展了对一致性的介绍。 在第  
9.1  节中,我们讨论了为更复杂的系统模型设计一致性协议所涉及的问题。 在第  9.2  节中,
我们描
述了适用于侦听和目录协议的优化。 在第  9.3  节中,我们解释了如何确保一致性协议保持有效(即
避免死锁、 活锁和饥饿)。 在第  9.4  节中,我们介绍了令牌一致性协议[12], 这是一类包含侦听和
目录协议的协议。 我们在第9.5节中总结了一致性的未来。

9.1  系统模型
到目前为止, 我们假设了一个简单的系统模型, 其中每个处理器内核都有一个物理寻址的单级回
写数据缓存。 该系统模型省略了商业系统中通常存在的许多功能, 例如指令缓存(第9.1.1  节)、
翻译后备缓冲区(第9.1.2  节)、 虚拟寻址缓存(第9.1.3  节)、
直写缓存(第  9.1.4节)、一致
性  DMA(第9.1.5  节)
和多级高速缓存(第9.1.6  节)。

9.1.1  指令缓存
所有现代内核都至少有一级指令缓存,
这就提出了是否以及如何支持指令缓存一致性的问题。 虽
然真正的自修改代码很少见,
但包含指令的缓存块可能会在操作系统加载程序或库、
即时  (JIT)  
编译器生成代码或动态运行时系统重新优化程序时被修改。
程序。

将指令缓存添加到一致性协议表面上很简单; 指令缓存中的块是只读的,
因此处于稳定状
态  I  或  S。
此外,
内核从不直接写入指令缓存; 核心通过对其数据缓存执行存储来修改代码。因此,
指令缓存的一致性控制器仅在它观察到从另一个缓存(可能是它自己的  L1  数据缓存) 到状态  
S  中的块的  GetM  时才采取行动,
并简单地使该块无效。

指令高速缓存一致性不同于数据高速缓存一致性有几个原因。
最重要的是,
一旦获取,

条指令可能会在内核的流水线中缓存很多次
Machine Translated by Google

192  9.  连贯性的高级主题

周期(例如, 考虑一个内核,它用一长串加载填充其  128  条指令窗口,
每个指令都错过了到  DRAM  的
所有路径)。 修改代码的软件需要某种方式来了解写入何时影响了获取的指令流。 一些架构, 例如  
AMD  Opteron,
使用一个单独的结构来解决这个问题, 该结构跟踪哪些指令在管道中。 如果此结构检
测到正在执行的指令发生更改, 则会刷新管道。

然而,由于指令的修改频率远低于数据, 因此其他架构要求软件明确管理一致性。 例如,


Power架构提
供了icbi(instruction  cache  block  invalidate)
指令来使一个指令缓存条目失效。

9.1.2  翻译后备缓冲器  (TLB)
转换后备缓冲区  (TLB)  是保存特殊类型数据的缓存: 从虚拟地址到物理地址的转换。 与其他缓存一样,
它们必须保持一致。 与指令缓存一样, 它们在历史上没有参与处理数据缓存的相同全硬件一致性协
议。  TLB  一致性的传统方法是TLB  shootdown  [18],
这是一种软件管理的一致性方案, 可能有也可
能没有一些硬件支持。 在经典实现中, 核心使转换条目无效(例如, 通过清除页表条目的  PageValid  
位)
并向所有核心发送处理器间中断。 每个核心接收其中断, 陷入软件处理程序, 使其  TLB  中的特定翻
译条目无效或刷新其  TLB  中的所有条目(取决于平台)。 每个核心还必须确保没有正在运行的指令
使用现在过时的翻译, 通常是通过刷新管道。 然后每个内核使用处理器间中断向发起内核发送确认。 在
修改翻译(或重新使用物理页面) 之前, 发起核心等待所有确认, 确保所有陈旧的翻译条目都已失效。
某些架构提供特殊支持以加速  TLB  击落。 例如, Power  架构通过使用特殊的  tlbie(TLB  无效条目)
指令消除了代价高昂的处理器间中断; 发起核心执行一条  tlbie  指令, 该指令将无效的虚拟页号广播
到所有核心, 并且仅在所有核心都完成无效后才完成。

最近的研究建议消除  TLB  击落, 而是将  TLB  合并到现有的数据和指令缓存的全硬件一致性协
议中[16]。
这种全硬件解决方案比  TLB  shootdown  更具可扩展性, 但它需要对  TLB  进行修改,
以使其能够以
与数据和指令缓存相同的方式进行寻址。 也就是说, TLB  必须探听在内存中保存翻译的块的物理地址。

9.1.3  虚拟缓存
当前系统中的大多数缓存 以及本入门手册中迄今为止讨论的所有缓存 都是通过物理地址访问的,
但也可以通过虚拟地址访问缓存。
我们在图9.1  中说明了这两种选择。
虚拟寻址缓存(“虚拟缓存”)
具有一个关键优势
Machine Translated by Google

9.1.系统模型  193

虚拟地址
核 缓存

(a)  虚拟寻址缓存

虚拟地址 实际地址
核 TLB 缓存

(b)  物理寻址缓存

图  9.1:
物理与虚拟寻址缓存。

关于物理寻址缓存(“物理缓存”):地址转换的延迟不在关键路径上。
1这种延迟优势
对延迟很关键的一级缓存很有吸引力,
但对于较低级别的缓存通常不太有吸引力延迟不那
么重要。
然而,
虚拟缓存给一致性协议的架构师带来了一些挑战:
‧  一致性协议总是在物理地址上运行,
以与主内存兼容,
否则主内存将需要自己的  
TLB。因此,
当一致性请求到达虚拟缓存时,
请求的地址必须进行反向转换以获得访问
缓存的虚拟地址。

‧  虚拟缓存引入了同义词问题。
同义词是映射到同一物理地址的多个虚拟地址。如果没
有避免同义词的机制, 同义词可能同时存在于虚拟缓存中。
因此,不仅虚拟高速缓存需
要反向转换机制, 而且任何给定的反向转换都可能导致多个虚拟地址。

由于实现虚拟缓存的复杂性,它们很少在当前系统中使用。
但是,
它们已在许多早期系
统中使用,并且它们有可能在未来再次变得更加相关。

9.1.4  直写缓存
我们的基线系统模型假设写回  L1  数据缓存和共享写回  LLC。
另一种选择,
直写缓存,有几个优点和缺点。 明显的缺点包括显着增加带宽和将数据写入到

1  虚拟索引和物理标记的缓存具有相同的优点,
而没有虚拟缓存的缺点。
Machine Translated by Google

194  9.  连贯性的高级主题

内存层次结构的下一个较低级别。
在现代系统中,
这些缺点有效地限制了对  L1  缓存的直写/回写决策。

直写  L1  的优点包括以下内容。

‧  明显更简单的两态VI(有效和无效)
一致性协议。
存储写入  LLC  并使其他缓存中的所有有效副
本无效。

‧  除了将  L1  状态更改为  Invalid  之外,
L1  驱逐不需要任何操作,
因为
LLC  始终持有最新数据。

‧  当  LLC  处理一致性请求时,
它可以立即响应,
因为它始终拥有最新数据。

‧  当L1  观察到另一个内核的写入时,
它只需要将缓存块的状态更改为Invalid。
重要的是,
这允许  
L1  用一个单一的、 可清除的触发器来表示每个块的状态,
从而消除了复杂的仲裁或双端口状态  
RAM。

‧  最后,
直写缓存也有助于容错。 尽管详细讨论超出了本入门的范围, 但直写式  L1  缓存永远不会
保存块的唯一副本, 因为  LLC  始终保存有效副本。
这允许  L1  仅使用奇偶校验, 因为它总是可以
使具有奇偶校验错误的块无效。

直写缓存对多线程内核和共享  L1  缓存提出了一些挑战。 回想一下,
TSO  需要写原子性, 因此所
有线程(每个形成存储的线程除外) 必须同时看到存储。 因此,
如果两个线程  T0  和  T1  共享同一个  L1  
数据缓存, 则  T0  对块  A  的存储必须阻止  T1  访问新值,
直到其他缓存中的所有副本都已失效(或更
新)。 尽管存在这些复杂性和劣势, 但仍有一些设计使用直写式  L1  高速缓存, 包括  Sun  Niagara  处理
器和  AMD  Bulldozer。

9.1.5  相干直接存储器访问(DMA)
在第2  章中,当我们第一次介绍一致性时, 我们观察到只有当有多个参与者可以读写缓存和内存时才会
出现不一致性。 今天,最明显的参与者集合是单个芯片上的多个内核, 但缓存一致性问题首先出现在具
有单核和直接内存访问  (DMA)  的系统中。  DMA  控制器是在显式系统软件控制下读写内存的参与者,
通常以页面粒度。 读取内存的  DMA  操作应该找到每个块的最新版本, 即使块驻留在状态  M  或  O  的高
速缓存中。 类似地,写入内存的  DMA  操作需要使块的所有陈旧副本无效。

通过向  DMA  控制器添加一个一致性高速缓存并因此让  DMA  参与一致性协议来提供一致性  
DMA  是很直接的。 在这样的模型中, 一个
Machine Translated by Google

9.1.系统模型  195

DMA  控制器与专用内核没有区别, 保证  DMA  读取始终找到块的最新版本, 而  DMA  写入将使所有陈


旧副本无效。
但是, 出于多种原因, 不希望向  DMA  控制器添加一致的高速缓存。
首先, DMA  控制器具有与传统内核截然不同的位置模式, 它们通过内存流式传输, 几乎没有(如果有
的话) 临时重用。 因此,
DMA  控制器很少使用大于单个块的高速缓存。 其次,当  DMA  控制器写入一个
块时, 它通常会写入整个块。 因此,使用  GetM  获取块是一种浪费,因为整个数据将被覆盖。 许多一致
性协议使用特殊的一致性操作来优化这种情况。

我们可以想象向本入门中的协议添加一个新的  GetM‑NoData  请求, 该请求寻求  M  许可, 但只需要


确认消息而不是数据消息。
其他协议使用特殊的  PutNewData  消息, 它更新内存并使所有其他副本无效, 包括  M  和  O  中的副
本。
通过要求操作系统有选择地刷新缓存, DMA  也可以在没有硬件缓存一致性的情况下工作。 例
如,
在启动到页面  P  或从页面  P  启动  DMA  之前,操作系统可以使用类似于  TLB  Shootdown  的协
议(或使用其他一些页面刷新硬件支持) 强制所有缓存刷新页面  P。 这种方法效率低下, 因此通常只
出现在某些嵌入式系统中, 因为操作系统必须保守地刷新页面, 即使它的任何块都不在任何缓存中。

9.1.6  多级缓存和层次一致性协议

我们的基准系统假设一个多核芯片具有两级缓存: 每个内核的私有一级数据  (L1)  缓存和包含数据
和指令  (LLC)  的共享最后一级内存端缓存。 但芯片和高速缓存的许多其他组合也是可能的。 例如,
Intel  Nehalem  和  AMD  Opteron  处理器支持具有多个多核芯片的系统以及额外级别的专用(每
核) L2  缓存。 图9.2说明了一个具有两个多核处理器的系统, 每个处理器都有两个内核, 在私有  L1  和
共享  LLC  之间具有私有  L2  缓存。

接下来我们将讨论单个多核芯片上的多级缓存、
具有多个多核处理器的系统以及分层一致性
协议。

多级缓存对于多级缓
存,
一致性协议必须确保所有这些缓存保持一致。 也许最直接的解决方案是完全独立地处理每个缓
存。
例如,L1、
L2  和  LLC  可以各自独立处理所有传入的一致性请求;
这是  AMD  Opteron  [1]  所采用
的方法。

然而,我们也可以设计缓存层次结构,
这样并不是每个缓存都需要监听每个一致性请求。 正如
第8.6  节中所讨论的,一个关键的设计选项是是否包含以及包含哪些缓存。
如果  L2  包含  L1  中块的
超集, 则它是包容性的
Machine Translated by Google

196  9.  连贯性的高级主题

多核芯片 多核芯片

核 核 核 核

L1 L1 L1 L1

L2 L2 L2 L2

记忆 有限责任公司 有限责任公司
记忆

芯片间互连网络

图  9.2:
具有多个多核芯片的系统。

缓存。 考虑包容性  L2  的情况,
当  L2  从另一个核心侦听块  B  的  GetM  时。
如果  B  不在  L2  中,
则也不需要窥探  L1  缓存, 因为  B  不可能在其中任何一个中。 因此, 包容性  L2  缓存可以用作过
滤器, 减少必须由  L1  缓存侦听的一致性请求流量。 相反,如果  B  在  L2  中,那么  B  也可能在  
L1  缓存中,
然后  L1  缓存也必须监听请求。

这就是  AMD  Bulldozer  所采用的方法。
包含的好处 减少  L1  监听带宽 必须与包含块的冗余存储所浪费的空间进行权衡。 如
果缓存层次结构是独占的(即, 如果块在  L2  中,
则它不在  L1  缓存中)或非包含的(既不包含
也不排他), 则缓存层次结构可以容纳更多数量的不同块。 不提供包含的另一个原因是为了避
免维护包含的复杂性(即, 当  L2  逐出该块时, 使来自  L1  的块无效)。

多个多核处理器可以通过组
合多个多核处理器芯片来构建更大的系统。 虽然可扩展系统的完整处理超出了本入门书的范
围,
但我们将研究一个关键问题: 如何使用  LLC。
在单芯片系统中,LLC  是逻辑上与内存相关联
的内存端缓存,
因此就一致性而言, 可以在很大程度上忽略它。 在多芯片系统中, LLC  也可以被
视为存储器层次结构的另一层。 我们从给定芯片(“本地” 芯片)
及其本地内存的角度介绍选
项;
其他芯片是“远程”芯片。  LLC  可用作:
Machine Translated by Google

9.1.系统模型  197

‧  内存端缓存,
用于保存最近从本地内存请求的块。
请求可以仅来自本地芯片,
也可以来自本地
和远程芯片。
‧  内核端缓存,
用于保存芯片上内核最近请求的块。  LLC  中的块可以在这个芯片或其他芯片
上有家。 在这种情况下,
一致性协议通常必须在多个多核芯片的  LLC  和存储器之间运行。

LLC  也可以在混合方案中用于这两个目的。
在混合方法中,
架构师必须决定如何将  LLC  分配给这些不同的需求。

分层一致性协议前面章节中描述的
协议是平面协议,因为有一个单一的一致性协议,每个缓存控制器都以相同的方式对待。
然而,
一旦
我们引入了多级缓存,我们就引入了对分层一致性协议的可能需求。

一些系统自然是分层的,
包括由多个多核芯片组成的系统。在每个芯片内,可以有一个芯片内
协议,
也可以有一个跨芯片的芯片间协议。片内协议可以满足的一致性请求不与片间协议交互;只有
当一个请求不能被芯片上的另一个节点满足时,该请求才会被提升到芯片间协议。

一个级别的协议选择在很大程度上独立于另一级别的选择。 例如,
可以使芯片内侦听协议与
芯片间目录协议兼容。每个芯片都需要一个目录控制器, 该目录控制器将整个芯片视为目录协议中
的一个节点。芯片间目录协议可以与第  8  章中介绍的目录协议之一相同,
目录状态自然表示为一种
粗俗的时尚。另一种可能的分层系统可以具有用于芯片内和芯片间协议的目录协议, 并且这两个目
录协议可以相同甚至彼此不同。

分层系统的分层协议的一个优点是它可以为商品芯片设计一个简单的、
可能不可扩展的芯片
内设计。在设计芯片时,
最好不必设计可扩展到系统中可能存在的最大可能内核数的单一协议。
对于
绝大多数由单芯片组成的系统来说,这样的协议可能有点矫枉过正。

分层系统的分层协议有很多例子。  Sun  Wildfire  原型[5]将多个侦听系统与更高级别的目
录协议连接在一起。  AlphaServer  GS320  [4]有两层目录协议,
一层在每个四处理器块内,
另一层
跨越这些块。斯坦福  DASH  机器[10]由多个通过更高级别的目录协议连接的侦听系统组成。

在具有数百或数千个内核的系统中,拥有单一一致性协议可能没有多大意义。
系统可能更可
能被静态或动态地划分为多个域,每个域运行一个单独的工作负载或单独的虚拟机。在
Machine Translated by Google

198  9.  连贯性的高级主题

在这样的系统中, 实现一个优化域内共享但仍允许域间共享的分层协议可能是有意义的。  Marty  
和  Hill  [13]最近的工作是在具有平面设计的多核芯片之上叠加分层一致性协议。
这种设计使常见情
况(域内共享) 变得快速,同时仍允许跨域共享。

9.2  性能优化
优化一致性协议性能的研究由来已久。
我们没有提出高层次的调查,
而是关注两种优化,这两种优化在很大程度上独立于底层一致性协议
是监听还是目录。
为什么要进行这两个优化?因为它们可能是有效的,
并且它们说明了可能的优化类
型。

9.2.1  迁移共享优化
在很多多线程程序中, 一个普遍的现象就是迁移共享。 例如,
一个核心可能会读取然后写入一个数据
块,
然后第二个核心可能会读取并写入它, 依此类推。这种模式通常来自关键部分(例如,锁变量本
身),其中数据块从一个内核迁移到另一个内核。 在一个典型的协议中,每个核心执行一个  GetS  事
务来读取数据, 然后执行一个后续的  GetM  事务来获得对同一块的写入权限。 但是,如果系统可以预
测数据符合迁移共享模式, 则核心可以在首次读取块时获得该块的独占副本, 从而减少访问数据的延
迟和带宽  [2,  15 ,  17 ] .迁移优化与E状态优化类似,不同之处在于当块在某个缓存中处于状态M时,
它还需要返回一个独占副本, 而不仅仅是当块在所有缓存中处于状态I时。

有两种优化迁移共享的基本方法。 首先,
可以使用一些硬件预测器来预测特定块表现出迁移共
享模式,并在加载未命中时发出  GetM  而不是  GetS。
这种方法不需要更改一致性协议,
但会带来一
些挑战:

‧  预测迁移共享: 我们必须设计一种硬件机制来预测块何时进行迁移共享。 一种简单的方法是


使用一个表来记录哪些块首先使用  GetS  获得,
然后随后写入,需要  GetM。在每次加载未命中
时, 一致性控制器可以咨询预测器以确定块是否表现出迁移共享模式。 如果是这样, 它可以发
出  GetM  请求,
而不是  GetS。

‧  错误预测:
如果一个块没有迁移,那么这种优化会损害性能。
考虑一个系统的极端情况, 其中核心仅发出  GetM  请求。
这样的系统永远不会允许多个内核共
享处于只读状态的块。
Machine Translated by Google

9.2.性能优化  199

或者, 我们可以使用额外的迁移  M  (MM)  状态扩展一致性协议。 从连贯性的角度来看, MM  


状态等同于状态  M(即脏、 排他、 拥有),但它表示该块是由  GetS  响应于预测的迁移共享模式而
获得的。 如果本地内核继续修改  MM  中的块, 加强迁移共享模式, 它将块更改为状态  M。如果处于
状态  M  的内核从另一个内核接收到  GetS, 它预测访问将是迁移的并发送独占数据(使其自己的
副本无效)。 如果  Other‑GetS  找到处于状态  MM  的块,
则迁移模式已被打破, 核心将发送共享
副本并恢复到  S(或可能是  O)。 因此,
如果许多核心发出  GetS  请求(没有后续存储和  GetM  
请求), 所有核心都将收到  S  副本。

迁移共享只是一种现象的一个例子,
如果检测到这种现象,可以利用它来提高一致性协议的
性能。
已经有许多针对特定现象的方案,以及更通用的预测相干事件的方法[14]。

9.2.2  错误共享优化
可能困扰一致性协议的一个性能问题是错误共享。 当两个内核正在读取和写入恰好驻留在同一缓
存块上的不同数据时,就会发生错误共享。即使内核实际上并未共享块上的数据(即,共享是错误
的),块的内核之间也可能存在大量的一致性流量。当核心等待访问块的一致性权限时,这种一致
性流量会损害性能,并且会增加互连网络上的负载。发生错误共享的可能性是块大小的函数 更
大的块可以容纳更多不相关的数据,因此更大的块更容易发生错误共享 以及工作负载。 至少有
两个优化可以减轻错误共享的影响。

子块一致性
在不减小块大小的情况下,
我们可以在更精细的子块粒度上执行一致性[7]。
因此,
高速缓存中的块对于不同的子块可能具有不同的一致性状态。
子块减少了错误共享,
但它
需要每个块的额外状态位来保存子块状态。

推测架构师
可以开发一种硬件机制来预测缓存中无效的块何时成为错误共享的受害者[6]。
如果预测者认为
该块因虚假共享而无效,
则核心可以推测性地使用该块中的数据,直到获得该块的一致性权限。 如
果预测是正确的,
这种推测克服了错误共享的延迟损失,但它并没有减少互连网络上的流量。
Machine Translated by Google

200  9.  连贯性高级主题

9.3  保持活力
在第2  章中,我们定义了一致性和一致性协议必须维护的不变量。这些不变量是安全不变量;
如果这些不变量得到维护, 那么协议将永远不会允许不安全(不正确)的行为。开玩笑地说,
提供安全很容易, 因为未插电的计算机永远不会做任何不正确的事情!关键是同时提供安全
性和活性, 其中提供活性需要防止三类情况:死锁、
活锁和饥饿。

9.3.1  死锁

正如第  8.2.3  节中简要讨论的那样, 死锁是指两个或多个参与者等待对方执行某些操作, 因
此永远不会取得进展的情况。 通常,死锁是由资源依赖循环引起的。 考虑两个节点  A  和  B  以
及两个资源  X  和  Y  的简单情况。假设  A  持有资源  Y,
B  持有资源  X。如果  A  请求  X,
B  请求  Y,
那么除非一个节点放弃它已经持有的资源, 否则这两个节点会死锁。 我们在图  9.3  中说明了
这个循环依赖图。 更一般地说,死锁可能是由涉及许多节点和资源的循环引起的。 请注意, 当
其他节点等待死锁节点执行某些操作(例如, 节点  C  请求资源  X)
时, 部分死锁(例如, 仅节
点  A  和  B  之间的简单死锁情况)会很快变成完整的系统死锁。

A  要求  X
A X

A持有Y B持有X

和 乙
B  请求  Y

图  9.3:
由于循环资源依赖导致的死锁示例。
圆圈是节点,
正方形是资源。
以资源结束的弧表
示对该资源的请求。 从资源开始的弧表示该资源的持有者。

协议死锁在一致性
协议中, 死锁可能出现在协议级别、 缓存资源分配和网络中。 当一致性控制器等待永远不会
发送的消息时, 会出现协议死锁。
例如, 考虑一个(有问题的) 目录协议, 它在发送  PutS  后不
等待  Put‑Ack,
而是立即转换到状态  I。
如果目录控制器发送  Inv(例如,响应核心  C1  的  
GetM  请求) 同时到核心C0
Machine Translated by Google

9.3.维持活力  201

当核心  C0  向目录发送  PutS  时,
C1  将永远不会从核心  C0  获得  Inv‑Ack,
并且会死锁等待它。
这种死锁代表协议错误, 通常由未经测试的竞争条件引起。

缓存资源死锁当缓存控制
器必须在执行某些操作之前分配资源时, 就会出现缓存资源死锁。这些死锁通常在处理另一
个内核的请求或写回时出现。 例如,
考虑一个高速缓存控制器,它有一组共享缓冲区(例如,事
务缓冲区条目或  TBE),
当内核发起一致性请求和为另一个内核的请求提供服务时,这些共享
缓冲区可能会被分配。 如果核心发出足够的一致性请求来分配所有缓冲区,那么它无法处理另
一个核心的请求, 直到它完成自己的请求。如果所有核心都达到这个状态,那么系统就会死锁。

协议相关的网络死锁  网络死锁有两个原
因:
错误的路由算法导致的死锁, 它与消息类型和一致性协议无关,
以及由于在一致性协议操作
期间交换特定消息而引起的网络死锁.我们在这里重点关注后一类协议相关的网络死锁。考虑
一个目录协议,
其中请求消息可能导致转发请求, 而转发请求可能导致响应。
该协议必须确保
三个不变量以避免循环依赖, 从而避免死锁。

边栏: 虚拟网络我们可以使
用不同的虚拟网络,而不是使用物理上不同的网络。 考虑通过单个点对点链路连接的两个
核心。在每个链接的末端是一个  FIFO  队列,
用于在接收核心处理传入消息之前保存它们。

这个单一的网络如图  9.4a  所示。
要添加另一个物理网络,
如图9.4b  所示,
我们复制链接和  
FIFO  队列。
请求在一个物理网络上传输, 而回复在另一个物理网络上传输。

为了避免复制链路和交换机(图中未显示交换机) 的成本,
我们可以添加一个虚拟
网络,
如图9.4c  所示。
虚拟网络的唯一成本是网络中每个交换机和端点的额外  FIFO  队列。
在此示例中添加第二个虚拟网络允许请求不会卡在回复之后。

虚拟网络与虚拟通道  [3]  相关,
一些论文可以互换使用这两个术语。但是,
我们更愿
意区分它们,因为它们解决的是不同类型的死锁。 虚拟网络防止不同类别的消息相互阻塞,
从而避免消息级死锁。

无论消息类型如何,在网络级别都使用虚拟通道来避免由于路由而导致的死锁。
为了
避免路由死锁,消息在多个
Machine Translated by Google

202  9.  连贯性高级主题

虚拟通道(例如,
在  2D  环面中向西传播的消息可能需要使用虚拟通道  2)。
一个虚拟通
道,
就像一个虚拟网络, 在网络中的每个交换机和端点被实现为一个额外的  FIFO  队列。

拟通道与虚拟网络正交; 每个虚拟网络可能有一定数量的虚拟通道以避免路由死锁。

请求一个

回复  B 核心C1

回复  B 请求一个 核心C1

核心C2 请求  B

核心C2 请求  B 回复  A


回复  A

一个网络(一) 两个物理网络  (b)

要求 请求一个

回复  B 核心C1
回复

核心C2 请求  B 要求

回复  A
回复

两个虚拟网络  (c)

图  9.4:
虚拟网络。

‧  如第  8.2.3  节所述,
每个消息类别必须在其自己的网络上传输。 网络可以是物理的或虚
拟的(请参阅“虚拟网络” 的边栏),但关键是要避免在  FIFO  缓冲区中一类消息卡在
另一类消息后面的情况。 在此示例中,
请求、转发的请求和响应
Machine Translated by Google

9.3.维持活力  203

所有在不同的网络上旅行。
因此,
一致性控制器具有三个输入  FIFO,
每个网络一个。

‧  消息类别必须具有依赖顺序。 如果  A  类消息可以导致一致性控制器发出  B  类消息,
则一
致性控制器在等待  A  类消息时可能不会停止对传入  B  类消息的处理。在目录示例中, 一致
性控制器不能在等待请求到达时停止对转发请求的处理, 也不能在等待转发请求时停止响
应处理。

‧  这个依赖链中的最后一个消息类 在这个目录示例中的响应消息 必须总是“沉没”。


也就是说, 如果一致性控制器接收到响应消息,
则必须没有消息类可以阻止它从传入队列
中删除。

这三个不变量消除了循环的可能性。即使请求在等待响应或转发请求时可能会停止,
但每
个请求最终都会被处理,因为响应和转发请求的数量受未完成事务数量的限制。

9.3.2  活锁

活锁是一种情况,
其中两个或多个参与者执行操作并更改状态, 但从未取得进展。  Livelock  是
饥饿的一个特例,
接下来会讨论。活锁最常发生在使用否定确认消息  (NACK)  的一致性协议中。
节点可能会发出一致性请求,但会收到  NACK,
提示重试。
如果与另一个节点的争用或某些可重复
的竞争导致这种情况无限期地重复发生, 那么节点就会活锁。本入门中的协议不使用  NACK, 因
此我们关注另一个著名的活锁场景,涉及这些协议中可能出现的一致性权限。

活锁的原因就是所谓的“漏洞窗口” 问题[8],
表  9.1  中举例说明了这一问题。 考虑一个监听
协议, 其中  Core  C1  发出对块  B  的  GetS  请求并将  B  的状态更改为ISAD  (在  I  中,
转到  S, 等
待  Own‑GetS  和数据)。 稍后, C1  在总线上观察到它的  Own‑GetS, 并将  B  的状态更改为状态
ISD。在  B  进入  ISD  状态和  C1  收到数据响应之间, C1  很容易观察到总线上另一个内核对  B  的  
GetM  请求。 在优化的协议中, 如第  7.5.5  节中的协议, 如果  GetM  到达处于ISD  状态的  B,  C1  
会将  B  的状态更改为ISDI。 在这种过渡状态下, 当C1稍后收到数据响应时, C1将B的状态更改为I。
由于C1无法对I中的块执行加载, 因此它必须为B发出另一个GetS。 但是,下一个GetS同样容易受
到影响漏洞问题的窗口, 因此  C1  可能永远不会取得进展。 核心仍然活跃, 因此系统没有陷入僵
局, 但永远不会向前发展。 有点反常, 这种情况最
Machine Translated by Google

204  9.  连贯性的高级主题

表  9.1:
核心  C1  尝试在侦听协议中加载块  B  的活锁示例

循环事件(都是Block  B) B  的核心  C1  状态

0 初始状态 我

1  加载请求,
向总线发出  GetS 父亲们

2  在总线上观察Own‑GetS 新闻处

3  在总线上观察Other‑GetM ISDI
4个 接收  Own‑GetS  的数据 我

5个 重新发出  GetS  到总线 父亲们

6  在总线上观察Own‑GetS 新闻处

7  在总线上观察Other‑GetM ISDI
8个 接收  Own‑GetS  的数据 我

9 等等(永远不会完成负载)

可能出现在高度竞争的块中, 这意味着大多数或所有核心可能同时被卡住, 因此系统可能会


活锁。
通过要求  C1  在收到数据响应时至少执行一次加载, 可以关闭此漏洞窗口。这个负载
在逻辑上似乎发生在  C1  的  GetS  被命令时(例如,
在侦听协议中的总线上),因此不违反
一致性。
但是,
如果不满足某些条件, 执行此加载可能会违反内存一致性模型。 满足这些条件有时被
称为躲猫猫问题, 我们将在边栏中更详细地讨论它。

边栏:
躲猫猫一致性问题
表9.2说明了有时称为  Peekaboo  Coherence  的问题。 在本例中, 位置  A  和  B  最
初为零,核心  C0  先写入  A  再写入  B,
核心  C1  先读取  B  再读取  A。
在  SC  和  TSO  内存
一致性模型下, 唯一的非法结果是  r1=  1  和  r2=0。 此示例执行使用第  8.7.2  节中的优化
目录协议, 但省略了目录控制器的操作(与示例无关)。  PrefetchS  是此示例中的一
个新操作, 如果可读块尚未驻留在缓存中, 它会发出  GetS  请求。

当一个块被预取, 在收到许可之前无效, 然后一个请求引用(由加载或存储请求,


而不是预取) 时,就会出现  Peekaboo  问题。
如果我们在预取但已经失效的数据到达时
执行需求参考, 那么需求参考在块被验证时有效排序。 在此示例中,
C1  的负载  A  在时
间  4  有效地订购(当  C1  收到
Machine Translated by Google

9.3.维持活力  205

Inv  用于块  A),
而  C1  的较早(按程序顺序)加载  B  在时间  7  被排序。
对这两个加载进行重
新排序违反了  SC  和  TSO。 请注意,无论预取操作是由显式预取指令、 硬件预取器还是推测
执行产生的, 都会出现此问题。 这个问题也可能出现在优化的侦听协议中, 例如第7.5.5  节中
的协议。

躲猫猫问题最简单的解决方案是在漏洞窗口执行加载, 当且仅当该加载是首次发出一
致性请求时程序顺序中最旧的加载。 换句话说, 只有在首次发出对  Peekaboo  加载的一致性
请求  [11]  之前,
已经按程序顺序执行了  Peekaboo  加载之前的所有先前加载和存储,
Peekaboo  加载才被允许访问无效数据。 在  Manerkar  等人的文章中可以找到为什么这个
解决方案足够的完整分析。  [11], 但从直觉上讲, 如果一个核心问题连贯性按照需求未命中
的顺序一次请求一个, 那么问题就不会出现。

表  9.2:  Peekaboo  Coherence  问题示例

核心C0 核心C1

A  =  B  =  0  最初 Prefetch  A(只读访问预取)
时间
商店  A  =  1 负载  r1  =  B
商店  B  =  1 负载  r2  =  A

答:
M[0] 答:

0
B:M[0] 双
1个
A:
prefetchS  miss,
issue  GetS/ISD
2  A:
接收Fwd‑GetS,
发送Data[0]/S
3  A:
店小姐;
问题  GetM/SMAD
4  A:
接收数据[0](ack=1)/SMA A:
接收Inv,
发送Inv‑Ack/ISDI

5  A:
收到Inv‑Ack,
执行store/M[1]  6
B:
商店命中率/M[1]
7 B:
加载未命中,
发出  GetS/ISD
8个
B:
接收Fwd‑GetS,
发送Data[1]/S[1]
9 B:
接收Data[1],
执行load  r1=1/S[1]
10 A:
加载未命中,
停止/ISDI  A:
11 接收数据[0],
执行加载  r2=0/I  Core  C1  观察到  A  =  
0  和  B  =  1,
有效地重新排序负载。
Machine Translated by Google

206  9.  连贯性高级主题

存储到IMDS、  IMDSI  或IMDI  中的块也存在同样的漏洞窗口。
在这些情况下, 块的存储永远不会执行, 因为事务结束时块的状态是  I  或  S,
这不足以执行存储。 幸
运的是, 我们为ISDI中的负载提供的相同解决方案适用于这些州的商店。 发出  GetM  的核心在接收
到数据时必须至少执行一次存储, 并且该核心必须将这个新写入的数据转发给在观察到自己的数
据之间请求  S  和/或  M  中的块的其他核心GetM  并在它收到数据时响应它的  GetM。 请注意,避免  
Peekaboo  问题所需的相同限制仍然适用: 即,
当且仅当存储是发出一致性请求时程序顺序中最
旧的加载或存储时才执行存储。

9.3.3  饥饿
饥饿是一种情况,
其中一个或多个核心无法前进,
而其他核心仍在积极前进。
没有取得进展的核心
被认为是饥饿的。
饥饿有多种根本原因,
但它们往往分为两类:
不公平的仲裁和不正确地使用否定
确认。

当至少一个核心无法获得关键资源时,
就会出现饥饿,
因为该资源总是由其他核心获得或
持有。
一个典型的例子是基于总线的侦听协议中的不公平总线仲裁机制。 考虑以固定优先级顺序
授予对总线的访问权限的总线。
如果  Core  C1  希望提出请求,
它可以提出请求。

如果  C2  想提出请求, 只有在  C1  没有首先请求总线的情况下,
它才可以提出请求。  C3  必须服从  
C1  和  C2  等。
在这样的系统中, 低优先级的核心可能永远不会获得发出请求的许可, 因此会饿死。
这个众所周知的问题也有一个众所周知的解决方案: 公平仲裁。

另一类主要的饥饿原因是在一致性协议中错误使用否定确认  (NACK)。
在某些协议中, 接收
一致性请求的一致性控制器可能会向请求者发送  NACK(通常以动词形式用作“控制器  
NACKed  请求”),通知请求者请求未得到满足并且必须重新发布。协议通常使用  NACK  来简
化请求块正在处理其他交易的情况。 例如,
在某些目录协议中,
如果请求的块已经在事务中, 目录可
以  NACK  请求。

用  NACK  解决这些协议竞争条件似乎, 至少乍一看,在概念上比设计协议来处理这些罕见和复
杂的情况更容易。
然而, 挑战在于确保  NACKed  请求最终成功。无论有多少核同时请求同一个块,保证不出现饥饿
是一项具有挑战性的工作; 这本入门书的一位作者承认设计了一个带有导致饥饿的  NACK  的协
议。
Machine Translated by Google

9.4.代币一致性  207

9.4  代币一致性
直到最近,一致性协议还可以归类为侦听或目录, 或者可能是两者的混合。 每个类都有许多变体和几
个混合体,但协议从根本上讲是窥探和目录的某种组合。  2003  年, 马丁等人。提出了第三种协议分
类,
称为Token  Coherence  [12]。  Token  Coherence  (TC)  背后有两个关键思想。

TC  协议将令牌与每个块而不是状态位相关联。
每个区块有固定数量的代币,
核心可以交换
但不能创建或销毁 这些代币。 具有一个或多个块令牌的核心可以读取块,
并且具有块的所有令牌
的核心可以读取或写入块。

TC  协议由两个不同的部分组成: 正确性基础和性能协议。 正确性底层负责确保安全性(令牌


被保存) 和活跃性(最终满足所有请求)。 性能协议指定缓存控制器在缓存未命中时执行的操作。 例
如,
在  TokenB  性能协议中,
所有的一致性请求都是广播的。在  TokenM  协议中,
一致性请求被多播
到一组预测的共享者。

令牌一致性包含监听和目录协议, 因为监听和目录协议可以解释为  TC  协议。 例如,MSI  


snooping  协议相当于一个带有广播性能协议的TC  协议。  MSI  状态等同于具有块的所有/一些/无
令牌的核心。

9.5  一致性的未来
几乎自一致性发明以来,一些人就预测它很快就会消失, 因为它增加了硬件成本来存储额外的状态、
发送额外的消息并验证一切是否正确。然而, 我们预测一致性将继续得到普遍实施, 因为根据我们的
判断,
处理不一致性的软件成本通常是巨大的, 并且由更广泛的软件工程师群体承担, 而不是面临实
现一致性的少数硬件设计师。正如我们在下一章中讨论的那样, 这一主张的一个证据是在最近的异构
系统中采用了一致性。
然而,与我们在第  6  章到第  9章中讨论的协议不同,
我们将看到异构系统适用于
一致性导向而非一致性不可知的一致性协议。  2

9.6  参考文献
[1]  P.  康威和  B.  休斯。  AMD  Opteron  北桥架构。  IEEE微,  27(2):10–21,
2007  年  3  月/4  月。
DOI: 10.1109/mm.2007.43。  195

2  回想一下,
在以一致性为导向的一致性协议(第2.3  节)
中,一致性与一致性之间没有明确的分离。
Machine Translated by Google

208  9.  连贯性高级主题

[2]  AL  考克斯和  RJ  福勒。 用于检测迁移共享数据的自适应缓存一致性。
在过程中。 第  20  届计算机体系结构国际研讨会,
第  98‑108  页,
1993  年  5  月。
DOI:
10.1109/
isca.1993.698549。  198
[3]  WJ  达利。 虚拟通道流量控制。  IEEE  并行和分布式系统汇刊,  3(2):194–205,
1992  年  3  月。
DOI: 10.1109/71.127260。  201
[4]  K.  Gharachorloo、 M.  Sharma、
S.  Steely  和  SV  Doren。  AlphaServer  GS320  的架构和设
计。 在过程中。 第  9  届编程语言和操作系统架构支持国际会议, 第  13‑24  页,2000  年  11  月。
DOI:  10.1145/378993.378997。  197

[5]  E.  Hagersten  和  M.  Koster。  WildFire:
SMP  的可扩展路径。 在过程中。 第  5  届  IEEE高性能计
算机体系结构研讨会, 第  172–81  页,1999  年  1  月。
DOI:  10.1109/hpca.1999.744361。  
197
[6]  J.  Huh、
J.  Chang、
D.  Burger  和  GS  Sohi。
相干解耦:利用不相干。 在过程中。 第  11  届编程语言
和操作系统架构支持国际会议,  2004  年  10  月。 DOI:
10.1145/1024393.1024406。

199

[7]  M.  Kadiyala  和  LN  Bhuyan。
动态缓存子块设计, 减少虚假共享。 在过程中。
国际计算机设计会
议,  1995  年。 DOI:  10.1109/iccd.1995.528827。  199

[8]  J.  Kubiatowicz、
D.  Chaiken  和  A.  Agarwal。
关闭多阶段内存事务中的漏洞窗口。 在过程中。
第五届编程语言和操作系统架构支持国际会议, 第  274–84  页,
1992  年  10  月。
DOI:  
10.1145/143365.143540。  203

[9]  总部乐等。  IBM  POWER6  微架构。  IBM  研究与开发杂志
在撒谎,  51(6),  2007.  DOI:  10.1147/rd.516.0639。

[10]  D.  Lenoski、J.  Laudon、K.  Gharachorloo、A.  Gupta  和  J.  Hennessy。
用于  DASH  多处理器
的基于目录的缓存一致性协议。 在过程中。 第  17  届计算机体系结构国际研讨会, 第  148‑59  页,
1990  年  5  月。 DOI:  10.1109/isca.1990.134520。  197

[11]  YA  Manerkar、
D.  Lustig、
M.  Pellauer  和  M.  Martonosi。  CCICheck:使用hb图验证一致性‑
一致性接口。 在过程中。 第  48  届国际微架构研讨会,  2015  年。 DOI:10.1145/2830772.2830782。  
205

[12]  MMK  Martin、
MD  Hill  和  DA  Wood。
令牌一致性: 解耦性能和正确性。 在过程中。 第  30  届计算
机体系结构国际研讨会,  2003  年  6  月。 DOI:
10.1109/isca.2003.1206999。  191,  207
Machine Translated by Google

9.6.参考文献  209

[13]  马蒂先生和希尔博士。 支持服务器整合的虚拟层次结构。 在过程中。 第  34  届年度计算机体系结


构国际研讨会,  2007  年  6  月。
DOI:  10.1145/1250662.1250670。  198

[14]  SS  Mukherjee  和  MD  Hill。
使用预测来加速一致性协议。 在过程中。 第  25  届计算机体系结构国
际研讨会, 第  179‑90  页, 1998  年  6  月。
DOI:
10.1109/isca.1998.694773。  199

[15]  J.  尼尔森和  F.  达尔格伦。提高多处理器上事务处理工作负载的加载‑存储序列的性能。 在过程
中。 并行处理国际会议, 第  246–55  页,
1999  年  9  月。
DOI:
10.1109/icpp.1999.797410。  198

[16]  BF  Romanescu、
AR  Lebeck、
DJ  Sorin  和  A.  Bracy。
统一指令/翻译/数据  (UNITD)  一致性:
一个协议来统领它们。 在过程中。 第15  届国际高性能计算机体系结构研讨会,  2010  年  1  月。

DOI:
10.1109/hpca.2010.5416643。  192

[17]  P.  Stenström、 M.  Brorsson  和  L.  Sandberg。
为迁移共享优化的自适应缓存一致性协议。 在
过程中。 第  20  届年度国际计算机体系结构研讨会, 第  109–18  页,
1993  年  5  月。
DOI:
10.1109/
isca.1993.698550。  198

[18]  PJ  出纳员。 翻译后备缓冲区一致性。  IEEE计算机,  23(6),
第  26‑36  页,
1990  年  6  月。
DOI:
10.1109/2.55498。  192
Machine Translated by Google
Machine Translated by Google

211

第  10  章

一致性和连贯性
异构系统
这是专业化的时代。 今天的服务器、 移动和桌面处理器不仅包含传统的  CPU,
还包含各种加速器。 加速
器中最突出的是图形处理单元  (GPU)。 其他类型的加速器,包括数字信号处理器  (DSP)、
AI  加速器(例
如,
Apple  的神经引擎、
Google  的  TPU)、加密图形加速器和现场可编程门阵列  (FPGA)  也越来越普
遍。

然而,
这种异构架构带来了可编程性挑战。 如何在加速器内部和之间进行同步和通信? 而且,如何
有效地做到这一点?一个有前途的趋势是在  CPU  和加速器之间公开一个全局共享内存接口。回想一
下,
共享内存不仅通过为通信提供直观的加载‑存储接口来帮助实现可编程性, 而且还有助于通过对程
序员透明的缓存来获得局部性的好处。

CPU、
GPU  和其他加速器可以紧密集成并实际共享相同的物理内存,
就像移动片上系统中的情
况一样,或者它们可能具有物理独立的内存, 运行时提供共享内存的逻辑抽象.

除非另有说明, 我们假设前者。 如图  10.1  所示, 每个  CPU、 GPU  和加速器都可能有多个内核, 每个内核


都有私有的  L1  和一个共享的  L2。  CPU  和加速器还可以共享内存端末级缓存  (LLC), 除非另有说明,
否则该缓存是非包容性的。  (回想一下, 内存端  LLC  不会造成一致性问题)。  LLC  还用作片上存
储控制器。

共享内存自动提出问题。 什么是一致性模型? 如何(有效地)执行一致性模型?


特别是,
加速器
(L1)
内以及跨  CPU  和加速器(L1  和  L2)
的缓存如何保持一致?

在本章中,
我们将讨论这个快速发展领域中这些问题的可能答案。
我们首先研究加速器内的一致性和连贯性,
重点是  GPU。
然后我们考虑加速器和  CPU  之间的一致性
和连贯性。

10.1  GPU  的一致性和连贯性
我们从简要总结早期  GPU  架构及其编程模型开始本节。 这将有助于我们理解与此类  GPU  的一致性
和连贯性相关的设计选择, 这些  GPU  主要针对图形工作负载。
然后我们讨论
Machine Translated by Google

212  10.  异构系统的一致性和连贯性

中央处理器 显卡 加速器
核 核 SM SM 核 核

L1 ⋯⋯ L1 L1 ⋯⋯ L1 L1 ⋯⋯ L1

CTA  范围

L2 L2 L2

显卡范围

LLC  +  内存控制器

系统范围

图  10.1:
包含  CPU、
GPU  和加速器的异构片上系统的系统模型。  SM指的是流式多处理器;  
CTA指的是合作线程数组。

使用类似  GPU  的架构在所谓的通用图形处理单元  (GPGPU)  [5]  中运行通用工作负载的趋势。
具体来说, 我们讨论了  GPGPU  对一致性和连贯性提出的新要求。 然后,我们将详细讨论最近为
满足这些需求而提出的一些建议。

10.1.1  早期  GPU:
架构和编程模型
早期的  GPU  主要是为令人尴尬的并行图形工作负载量身定制的。
粗略地说,工作负载涉及独立
计算构成显示器的每个像素。 因此,
工作负载的特点是非常高的数据并行度和低度的数据共享、
同步和通信。

GPU  架构为了充
分利用并行性,
GPU  通常具有数十个称为流式多处理器  (SM)1  的内核, 如图10.1  所示。每个  SM  
都是高度多线程的,能够以一千个线程的数量级运行。
, 地暂存器内存(未显示)。
所有的  
映射到  
SS
M  
M  
共的 享一个  
线程共享  
L2  缓
L1  
存。
高速缓存和本

为了分摊所有这些线程获取和解码指令的成本, GPU  通常以称为  warp  的组执行线程。2  
warp  中的所有线程共享程序计数器  (PC)  和堆栈,
但仍可以独立执行使用掩码位的特定于线程
的路径, 这些掩码位指定  warp  中的哪些线程处于活动状态以及哪些线程不应执行。 这种并行方
式被称为“单指令多线程”

NVIDIA  用语中的  1SM。 在  AMD  的说法中也称为计算单元  (CU)。
2Warp  用  NVIDIA  的说法。 在  AMD  的说法中也称为  Wavefront。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  213

(SIMT)。 因为  warp  中的所有线程共享  PC,
所以传统上  warp  中的线程是一起调度的。然而,
最近,
GPU  开始允
许  warp  中的线程拥有独立的  PC  和堆栈, 从而允许线程被独立调度。 对于余下的讨论,
我们假设线程可以独立调
度。

由于图形工作负载不共享数据或频繁同步 同步和通信通常在较粗粒度级别上很少发生 早期的  GPU  


选择不为  L1  缓存实现硬件缓存一致性。  (他们没有强制执行  SWMR  不变量。)
硬件高速缓存一致性的缺失使
得同步对程序员来说是一个更棘手的前景, 正如我们接下来所看到的。

GPU  编程模型与  CPU  指令集
架构  (ISA)  类似,
GPU  也有虚拟  ISA。
例如,NVIDIA  的虚拟  ISA  称为并行线程执行  (PTX)。
此外,
还有更高级的语言框架。
两个这样的框架特别受欢迎:
CUDA  和  OpenCL。
这些框架中的高级程序被编译成虚拟  ISA, 然后在内核安装时被翻译成本地二进制文件。
内核
是指由  CPU  卸载到  GPU  上的工作单元, 通常由大量软件线程组成。

GPU  虚拟  ISA  和语言框架选择通过称为作用域[15]  的线程层次结构向程序员公开  GPU  体系结构的层次
性质。 与所有线程都“平等” 的  CPU  线程不同,来自内核的  GPU  线程被分组到称为协作线程阵列  (CTA)  的集群
中。
3  CTA范围是指来自同一  CTA  的线程集。
这样的线程保证映射到相同的  SM, 因此共享相同的  L1。
因此, CTA  范
围也隐含地引用所有这些线程共享的内存层次结构级别  (L14)。 一个GPU作用域指的是来自同一个

显卡。
这些线程可能来自与该  GPU  相同或不同的  CTA。
所有这些
线程共享  L2。
因此,GPU  范围隐含地引用了  L2。
最后,
系统范围是指整个系统中所有线程的集合。 这些线程可以来
自构成系统的  CPU、GPU  或其他加速器。
所有这些线程都可以共享一个缓存  (LLC)。
因此,
系统范围隐式引用  LLC,
或者如果没有共享  LLC, 则引用统一共享内存。

为什么要将线程和内存层次结构暴露给软件?在没有硬件缓存一致性的情况下, 这允许程序员和硬件有效
地合作实现同步。
具体来说,如果程序员确保两个同步线程在同一个  CTA  中,
则线程可以通过  L1  缓存高效地进行
同步和通信。

PTX  术语中的  3CTA。
在  OpenCL  中也称为工作组,
在  CUDA  中称为线程块。  4和本地暂
存存储器, 如果存在
Machine Translated by Google

214  10.  异构系统的一致性和连贯性

属于同一  GPU  内核的不同  CTA  的两个线程如何同步?
有点令人惊讶的是,
早期的  GPU  一致性模型并没有
明确允许这样做。
但在实践中,
程序员可以通过绕过  L1  并在共享  L2  同步的特制加载和存储来实现  GPU  范围内的同
步。
不用说,
这在程序员中引起了很大的混乱[8]。
在下一节中,
我们将通过示例详细探讨  GPU  一致性。

GPU  一致性GPU  支
持宽松的内存一致性模型。
与宽松的一致性  CPU  一样,
GPU  仅强制执行程序员指定的内存顺序,
例如,
通过  FENCE  
指令。
然而, 由于  GPU  中缺乏硬件缓存一致性,FENCE  指令的语义是不同的。 回想一下,多核  CPU  中的  FENCE  指
令确保在  FENCE  之前执行加载和存储, 相对于所有线程, 在  FENCE  之后的加载和存储之前。  GPU  FENCE  提供类
似的语义,
但仅针对属于同一  CTA  的其他线程。
其必然结果是  GPU  存储缺乏原子性。
回想一下,
存储原子性(第
5.5.2  节)
要求一个线程的存储在逻辑上同时被所有其他线程看到。
然而,
在  GPU  中,
存储可能先于其他线程对属于
同一  CTA  的线程可见。

考虑表10.1中所示的消息传递示例, 其中程序员在
加载  Ld2  倾向于读取新值而不是旧值  0。
如何确保这一点?

表  10.1:
消息传递示例。  T1  和  T2  属于同一个  CTA。

读T1 读T2 评论

St1:
St  数据  =  NEW; Ld1:  Ld  r1  =  fl  ag; //  初始数据和标志为  0。
栅栏; B1:
如果(r1≠SET)
转到Ld1;
St2:
St  标志  =  SET; 栅栏;
ld2:
ld  r2  =  数据; r2==0可以吗?

首先要注意的是,
没有这两条FENCE指令,
Ld2可以读到旧值
of  0放松的  GPU  可能会乱序执行加载和存储。
现在,
让我们假设两个线程  T1  和  T2  属于同一个  CTA,
因此映射到同一个  SM。
在微架构级别,
GPU  FENCE  
的工作方式类似于  XC  中的  FENCE,
正如我们在第5  章中讨论的那样。
重新排序单元确保  Load/Store!FENCE和
FENCE!Load/Store  排序得到执行。
因为  T1  和  T2  共享一个  L1,
遵守上述规则足以确保  T1  中的两个存储按程序
顺序对  T2  可见,
从而确保加载  L2  读取  NEW。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  215

表  10.2:
消息传递示例。  T1  和  T2  属于不同的  CTA。

读T1 读T2 评论

St1:
St.GPU  数据  =  NEW; Ld1:  Ld.GPU  r1  =  fl  ag;   //  初始数据和标志为  0。
栅栏;  St2:  St.GPU  标志  =   B1:
如果(r1≠SET) 转到Ld1;
SET; 栅栏;  ld2:  Ld.GPU  r2  =  数据;
r2==0可以吗?

另一方面, 如果两个线程  T1  和  T2  属于不同的  CTA,
因此映射到不同的  SM(SM1  和  SM2),
则负载  Ld2  有可能读取  0。
要了解如何读取, 请考虑以下事件序列.

‧  最初,
数据和标志都缓存在两个L1  中。

‧  St1和St2按程序顺序执行,
分别在SM1的L1写入NEW和SET
缓存。

‧  持有标志的高速缓存行从SM1  的L1  中被逐出,
flag=SET  被写入L2。

‧  清除SM2  的L1  中的缓存行持有标志。

‧  负载  Ld1  在  SM2  的  L1  中执行但未命中,
因此从  L2  获取行并
读取  SET。

‧  Ld2  执行负载,
命中  SM2  的  L1,
并读取  0。

因此,虽然  FENCE  确保这两个存储以正确的顺序写入  SM1  的  L1,
但在没有硬件缓存一致性
的情况下,它们可能以不同的顺序对  T2  可见。 这就是为什么早期的  GPU  编程手册明确禁止在同一内
核的线程之间进行这种类型的  CTA  间同步。

那么  CTA  间的同步就不可能实现了吗? 在实践中, 一种变通方法是可能的, 即利用直接针对内


存层次结构的特定级别的特殊加载和存储指令。 正如我们在表  10.2  中所见,  T1  中的两个存储绕过  
L1  显式写入  GPU  范围(即,
写入  L2)。同样,
来自  T2  的两个负载绕过  L1  并直接从  L2  读取。因此,
通过使用绕过  L1  的加载和存储, GPU  范围的两个线程  T1  和  T2  明确地在  L2  同步。

然而,
上述解决方法存在问题 主要问题是由于绕过  L1  的加载和存储导致性能低下。
在这个
简单的示例中,所讨论的两个变量必须通过  SM  进行通信,
因此绕过是不可避免的。

然而,
绕过更多变量可能会有问题,
其中只有一些变量是跨  SM  通信的。
在这种情况下,
程序员面临
着繁重的任务
Machine Translated by Google

216  10.异构系统的一致性和连贯性

小心地将加载/存储引导到适当的内存层次结构级别,
以便有效地使用  L1。

闪回测验问题  8:  GPU  不支持硬件缓存一致性。
因此,
他们无法强制执行内存一致性模型。

或错?
答:
假的!早期的  GPU  不支持硬件缓存一致性, 但支持范围松散的一致性模型。

总结: 限制和要求早期的  GPU  主要针对既
不频繁同步也不频繁共享数据的令人尴尬的并行工作负载。 因此,此类  GPU  选择不支持硬件缓
存一致性以保持本地缓存的一致性, 代价是仅允许  CTA  内同步的范围内存一致性模型。 更灵活
的  CTA  间同步要么效率太低,要么给程序员带来巨大负担。

程序员开始将  GPU  用于通用工作负载。 此类工作负载往往涉及相对频繁的细粒度同步和
更通用的共享模式。因此, 希望  GPGPU  具有:


严格而直观的内存一致性模型,
允许跨所有线程同步;


一种一致性协议,
它强制执行一致性模型,
同时允许高效的数据共享和同步,
同时保持传统  
GPU  架构的简单性,
因为  GPU  仍将主要满足图形工作负载。

10.1.2  大图:
GPGPU  的一致性和连贯性
我们已经概述了  GPGPU  一致性和连贯性所需的属性。 满足这些需求的一种直接方法是使用类似
多核  CPU  的方法来实现一致性和一致性, 即使用一种与一致性无关的一致性协议(我们在第  6  
章到第  9  章中详细介绍)来理想地实施强一致性顺序一致性  (SC)  等模型。
尽管勾选了几乎所有方
框 一致性模型当然是直观的(没有范围的概念) 并且一致性协议允许有效地共享数据 该方
法不适合  GPU。 这有两个主要原因[30]。

首先, 在写入时使共享器无效的类似  CPU  的一致性协议会在  GPU  上下文中产生高流量开
销。 这是因为  GPU  中本地缓存  (L1)  的总容量通常与  L2  的大小相当, 甚至更大。
例如,NVIDIA  
Volta  GPU  的总容量约为  10  MB  的一级缓存和只有  6  MB  的二级缓存。
一个独立的包含目录不
仅会由于重复的标签而产生大面积的开销, 而且还会带来很大的复杂性, 因为该目录将
Machine Translated by Google

10.1.  GPU  一致性和连贯性  217

需要高度关联。另一方面, 考虑到聚合  L1  的大小,
嵌入式包含目录会在  L2  驱逐时导致大量的召
回流量。
其次,
由于  GPU  维护着数千个活动的硬件线程, 因此需要跟踪相应大量的一致性事务, 这
将花费大量的硬件开销。

如果没有写入器启动的失效, 存储如何传播到其他非本地  L1  缓存?  (即,
如何使属于其
他  CTA  的线程可见存储?)
如果没有编写器启动的失效,
如何实施一致性模型 更不用说强一
致性模型了?

在第  10.1.3节和第  10.1.4  节中,
我们讨论了两个采用自失效[18]  的提议,
其中处理器使其
本地缓存中的行无效, 以确保来自其他线程的存储变得可见。

自失效协议可以有效执行哪种一致性模型?我们认为, 此类协议可以直接有效地执行宽松
的一致性模型,而不是执行与一致性无关的不变量,例如  SWMR  不变量。

一致性模型是否应包括范围仍在争论中[15、 16、
23 ] 。
尽管作用域增加了程序员的复杂性, 但可以说它们简化了一致性实现。 因为不能排除范围,
我们
在第10.1.4  节中概述了一个范围内的一致性模型,
该模型不将同步限制在范围的子集内。 值得注
意的是, 我们介绍的一致性模型在本质上类似于当今工业  GPU  中使用的一致性模型(它们都使
用不将同步限制在范围子集内的范围模型)。

10.1.3  时间一致性
在本节中, 我们讨论了一种基于自我失效的方法来增强一致性, 称为时间一致性[30]。
关键思想
是每个读取器在称为租约的有限时间段内引入一个缓存块, 在该时间结束时该块将自我失效。 我
们讨论时间一致性的两个变体: (1)  一个强制执行  SWMR  的一致性不可知变体, 在这个变体中,
写入者会停止, 直到该块的所有租约都到期;  (2)  一种更有效的一致性导向变体, 它直接强制执
行宽松的一致性模型, 在该模型中, FENCE  而不是  writers  停滞。
对于下面的讨论, 我们假设共享
缓存  (L2)  是包含性的:
共享  L2  中不存在的块意味着它不存在于任何本地  L1  中。

我们还假设  L1  使用直写/无写分配策略:
写入直接写入  L2(直写)
并且写入  L1  中不存在的块不
会在  L1  中分配块L1(无写分配)。

一致性不可知的时间一致性
与  CPU  一致性常见的写入器使非本地缓存中的所有共享器无效不同,
考虑一种让写入器等待
直到所有共享器都被驱逐的协议
Machine Translated by Google

218  10.  异构系统的一致性和连贯性

块。 通过让写入等待直到没有共享者,该协议确保在写入成功的那一刻没有并发的读者 从而强制执
行  SWMR。
作家怎么知道要等多久?也就是说,作者如何确定该块不再有共享者? 时间一致性通过利用全
球时间概念来实现这一点。 具体来说,它要求每个  L1  和  L2  都可以访问一个记录全球时间的寄存器。

在  L1  未命中时,
读取器预测它希望在  L1  中保留该块多长时间,并通知  L2  这个持续时间称为
租约。
每个  L1  缓存块都标有时间戳  (TS),
该时间戳保存该块的租约。 当前时间大于其租约的  L1  块的
读取被视为未命中。

此外,L2  中的每个块都标有时间戳。当一个读者在  L1  缓存未命中时查询  L2,
它通知  L2  它想
要的租约;  L2  根据不变量更新块的时间戳,
即时间戳在所有  L1  中持有该块的最新租约。

每次写入 即使该块存在于  L1  缓存中 都会写入  L2;


写入请求访问保存在  L2  中的块的时间
戳,
如果时间戳对应于未来的某个时间, 则写入会停止直到该时间。 这种停顿确保在  L2  中执行写入时
没有块的共享者,从而确保  SWMR。

例子。
为了理解时间一致性如何工作, 让我们考虑表10.3  中的消息传递示例, 暂时忽略  FENCE  指令。
让我们假设线程  T1  和  T2  来自两个不同的  CTA,
映射到两个不同的  SM(SM1  和  SM2),
具有单独
的本地  L1。

表  10.3:
消息传递示例。  T1  和  T2  属于不同的  CTA。

读T1 读T2 评论

St1:
St  数据  1  =  NEW; Ld1:  Ld  r1  =  fl  ag; //  最初所有变量都是  0。
St2:  St  data2  =  NEW; B1:
如果(r1≠SET)
转到Ld1;
栅栏; 栅栏;
St3:
St  标志  =  SET; ld2:
ld  r2  =  data2; r2  ==  0  可以吗?

我们在表  10.4  中说明了  SM1、 SM2  和共享  L2  的事件时间线。 最初,我们假设  flag、 data1  和  


data2  缓存在  SM2  的本地  L1  中,租约值分别为  35、 30  和  20。在时间=1, 执行St1。 由于  L1  使用直写/
无写分配策略, 因此向  L2  发出写请求。 由于data1在time=30之前在SM2的L1缓存中是有效的, 所以
写入停滞到这个时候。 在  time=31  时,写入在  L2  执行。 然后, St2  在时间=37  时向  L2  发出写入请求,
该请求在时间=42  时到达  L2。 此时  data2  (20)  的租约已经过期, 因此写入在  L2  执行而没有任何停
顿。 同理, St3在time=48时发出写请求, 将flag写入
Machine Translated by Google

10.1.  GPU  一致性和连贯性  219

表  10.4:
时间一致性:
表10.3中事件的时间线
/*  T1  和  T2(属于不同的  CTA)
分别映射到  SM1  和  SM2。  fl  ag  =  0(lease  =  35),
data1  =  
0(lease  =  30)
和data2  =  0(lease  =  20)
缓存在SM2的L1  */
时间  SM1 SM2 L2
1个
St1  发出写请求
6个 数据  1  的写入停止直到

数据  1  (30)  数据  1  =  新
31 写入;
确认发送到SM1

36 St1  完成
37 St2发出写请求
42 data2  =  新写的

out  stalling  since  current  
time  >  lease  for  data2  (20);
确认发送到SM1

47  St2  完成  48
St3  发出写入请求
50 标志  (35)  的  L1  租约已过期,

此  Ld1  发出读取
要求
53 fl  ag  =  SET  written  with  
out  stalling  since  current  
time  >  lease  for  fl  ag  (35);确
认发送到SM1
55 fl  ag  =  SET  读取;
获得新租约;
发送到  SM2  的  L1
58 St3  完成
60 ld1完成
61 data2(20)的L1租约到期,
Ld2发
出读请求

66 data2  =  新读取;
获得新租约;
发送
到  SM2  的  L1
71 ld2完成
Machine Translated by Google

220  10.异构系统的一致性和连贯性

L2  在  time=53  没有任何停顿, 因为此时  flag  的租约已经到期。
同时, 在time=50, Ld1检查其L1,发现lease  for  flag已经过期,
于是向L2发出读请求, 并在time=60完
成。 同样, Ld2也在time=61时向L2发出读请求, 从L2中读取NEW的期望值, 并在time=71时完成。因为
这种时间一致性的变体强制执行  SWMR, 所以只要  GPU  线程按程序顺序发出内存操作, 就会强制执
行  SC5。

协议规范。 我们分别在表10.5和10.6中给出了  L1  和  L2  控制器的详细协议规范。  L1  有两个稳定状
态: (I)nvalid  和  (V)alid。
L1  使用GetV(t)、  Write和WriteV(t)请求与  L2  通信。  GetV(t),
它带有一个时间戳作为参数,
要求指
定的块在请求的租用期内以有效状态进入  L1, 在该时间结束时该块将自我失效。

Write  请求要求将指定值写入  L2,
而不将块放入  L1。  WriteV(t)  请求用于写入一个在  L1  中已经有
效的块, 并带有一个时间戳,
该时间戳持有其当前租约作为参数。 出于演示的原因, 我们仅提供协议的高
级规范(以及本章中的所有其他协议)。 具体来说, 我们只显示稳定状态和稳定状态之间的转换

状态。

状态  I  中的负载导致  GetV(t)  请求被发送到  L2;
从  L2  接收到数据后,
状态转换为  V。
状态  I  中的
存储导致向  L2  发送写入请求。 在收到来自  L2  的确认后(表明  L2  已写入数据), 存储完成。 由于  L1  
使用不写分配策略, 数据不会被带到  L1, 块的状态保持在状态  I。

回想一下, 如果全局时间提前超过该块的租约, 则该块将变得无效 这在逻辑上由租约到期时  V  


到  I  的转换表示。 6  在存储到状态V  的块时,
WriteV(t)  请求是发送到L2。  WriteV(t)  的目的是利用这样
一个事实, 即如果块在写入器的  L1  中私密保存, 则写入不需要在  L2  停止。 相反, 写入可以简单地更新  
L2  和  L1,
并继续将块缓存在  L1  中,直到其租约用完。  WriteV(t)  请求携带时间戳的原因是为了确定
该块是否私密地保存在写入器的  L1  中, 我们将在接下来看到。

我们现在描述  L2  控制器。  L2  有四种稳定状态:  (I)nvalid,表示该块既不存在于  L2  中, 也不
存在于任何  L1  中;  (P)private, 表示该块恰好出现在其中一个  L1  中;  (S)hared,
表示该块可能出现
在一个或多个L1中; 和  (Exp)ired,
表示该块存在于  L2  中,
但在任何  L1  中均无效。 在状态  I  收到  
GetV(t)  请求后,
L2  从

5原始论文[30]认为  warp  等同于线程,而我们考虑更现代的  GPU  设置
可以单独调度线程的地方。 因此, 我们设置的一致性与他们的设置略有不同。
6值得注意的是,
在实际实现中,
硬件没有必要主动检测块已过期并将其转换为  I  状态 块已过期的
事实可能会在对该块的后续事务中延迟发现。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  221

内存, 根据请求的租约更新块的时间戳, 并转换到  P。


在状态  I  接收到  Write  后,
它从内存中获取块,

新  L2  中的值,
发回  ack,
并转换到  Exp,
因为它在任何  L1  中都无效。

在状态  P  中收到  GetV(t)  请求后,L2  响应数据,
如果请求的租约大于当前时间戳,则扩展时间
戳,
并转换到  S。
对于在状态  P  中收到的  WriteV(t)  请求,
有两种情况。 在简单的情况下,
唯一持有该块的  SM  私下写入
它;
在这种情况下, L2  可以简单地更新

表  10.5:
通过时间相干性执行  SWMR:
L1  控制器

驱逐
加载 店铺
/到期
将  GetV(t)  发送到  L2  
发送写到  L2
我 rec。
来自  L2  的数据
记录。
来自  L2  的  Write‑Ack
/在

将  WriteV(t)  发送到  L2  
V  读命中 ‑/我
rec。
来自  L2  的  Write‑Ack

表  10.6:
通过时间相干性执行  SWMR:
L2  控制器

L2:
得到V(t) 写 写V(t) L2:
驱逐
到期
发送  Fetch  到  Mem 发送  Fetch  到  Mem
记录。
来自内存的数据 记录。
来自内存的数据
我 发送数据到  L1 写
TS←t 向  L1  发送  Write‑Ack
/P /Exp
停滞
如果(t  =  TS)
(直到到期) /Exp
发送数据到  L1 写
P TS←max(TS,t) 停滞(直到到期) 发送  Write‑Ack  到  L1  else  
/S stall  (until  expiry)

发送数据到  L1 停滞 停止
小号
停滞(直到到期) /Exp
TS←max(TS,t) (直到到期) (直到到期)

果(脏)
发送数据到  L1
写 写 发送回写到内存
指数 TS←t
向  L1  发送  Write‑Ack  向  L1  发送  Write‑Ack 记录。
来自内存的确认
/P
/我
Machine Translated by Google

222  10.异构系统的一致性和连贯性

毫不拖延地阻止并用  ack  回复。 但是有一个棘手的极端情况, 其中来自私有块的  WriteV(t)  消息被


延迟, 以至于该块现在是私有的, 但在不同的  SM  中! 为了消除这两种情况的歧义, 每个  WriteV(t)  消
息都带有一个带有该块租约的时间戳, 并且与  L2  中保存的时间戳的匹配表明前一种情况很简单。 不
匹配表示后者, 在这种情况下, WriteV(t)  请求会停止, 直到块到期,
此时更新  L2  并将  ack  发送回  
L1。
另一方面, 在状态  S  中接收到的  WriteV(t)  或写入请求必须始终等待,直到块在  L2  的租约到期。
最后, L2  块只允许在  Exp  状态下被逐出, 因为写入必须知道要停止多长时间才能执行  SWMR。

总之,
我们看到了时间一致性如何使用租用读取和延迟写入来强制执行  SWMR。
结合按程序
顺序呈现内存操作的处理器,
时间一致性可用于强制执行顺序一致性  (SC)。

Consistency‑directed  Temporal  
Coherence如前所述, 时间一致性强制执行  SWMR, 但成本很高, 每次写入未过期的共享块都需要
在  L2  控制器处停止。 由于  L2  在所有线程之间共享,
在  L2  处的停顿会间接影响所有线程,
从而降低
整体  GPU  吞吐量。

回想一下,在2.3节中我们讨论了两类一致性接口: 一致性不可知和一致性导向。与一致性无
关的一致性接口通过在写入返回之前将写入同步传播到其他线程来强制执行  SWMR。 考虑到在  
GPU  设置中执行  SWMR  的成本,
我们可以探索一致性导向的一致性吗?也就是说,不是让写入对其
他线程同步可见, 我们能否在不违反一致性的情况下让它们异步可见? 具体来说,
诸如第5  章的  XC7
之类的宽松一致性模型仅要求程序员通过  FENCE  指令指示的内存顺序。 这样的模型允许写入异步
传播到其他线程。

考虑表10.3中显示的消息传递示例,  XC  仅要求  St1  和  St2  在  St3  可见之前对  T2  可见。

不要求在  St1  执行时它必须已经传播到  T2。
换句话说,
XC  不需要  SWMR。

因此, XC  允许时间一致性的变体, 其中只有  FENCE,


而不是写入,需要停止。
当写请求到达  L2  并且块
被共享时, L2  简单地回复线程启动写与块关联的时间戳。 我们将这个时间称为全局写入完成时间  
(GWCT), 因为这表示线程在遇到  FENCE  时必须停止的时间,以确保写入对所有线程全局可见。

对于映射到  SM  的每个线程,
SM  跟踪为每个线程停顿时间寄存器中的写入返回的最大  
GWCT。撞上栅栏后, 停止

7现在,
让我们假设内存模型中没有作用域。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  223

thread  until  this  time  确保  FENCE  之前的所有写入都变得全局可见。

例子。 我们在表10.7中为相同的消息传递示例(表10.3)
说明了事件的时间线, 但现在考虑了  
FENCE  指令的影响,因为我们正在寻求实施一个宽松的类似  XC  的模型。

在  time=1  时,由于  St1  的写入请求被发送到  L2。 在  time=6  时,
写入在  L2  上执行, 没有任
何停滞, 尽管那时它的租约  (30)  还没有到期;  L2  回复一个  GWCT  30, 它被记住在  SM1  的每线
程停顿时间寄存器中。 以类似的方式, 由于  St2  的写入请求在时间  =  12  时发出。 在时间  =  17  时,
写入在  L2  上执行, L2  回复  GWCT  为  20。
收到  GWCT  后, SM1  不会更新其停顿时间, 因为当前
值  (30)  更高。  FENCE  指令在时间  =  23  时执行并阻塞线程  T1  直到其停顿时间为  30。 在时间  
=  31  时,由于  St3  向  L2  发出写入请求, 并且在时间  =  36  时在  L2  执行写入.因为标志  (35)  的租
约已经过期, 所以  L2  不响应  GWCT, 并且  St3  在时间  =  41  时完成。 同时, 在  time=40,
Ld1  尝试
从  L1  读取标志, 但它的租约会在  time=35  时到期。 因此, 向L2发出标志的读取请求, 在时间=45时
从L2读取SET, 并在时间=50时完成。 以类似的方式, 由于  Ld2  的读取请求在时间  =  51  时发出, 在
时间  =  56  时从  L2  读取并在时间  =  57  时完成, 返回  NEW  的预期值。

协议规范。 一致性导向的时间一致性协议规范大部分类似于一致性不可知变体 我们在表10.8  


(L1  控制器)
和表10.9  (L2  控制器)
中以粗体突出显示差异。

与  L1  控制器(表10.8) 的主要区别在于来自  L2  的  Write‑Ack  现在携带  GWCT。 因此,



接收到  Write‑Ack  后,
如果传入的  GWCT  大于该线程当前保持的停顿时间, 则  L1  控制器会延长
停顿时间。  (回想一下, 在遇到  FENCE  时,
线程会停止,
直到停止时间寄存器中保存的时间为
止。)

与  L2  控制器(表10.9) 的主要区别在于写入请求不会导致停顿; 相反,


执行写入并返回  
GWCT  和  Write  Ack。
同样,
状态  S  中的  WriteV(t)  请求也不会停止。

时间相干性: 总结和局限性我们看到了时间相干性
如何用于实施  SWMR  或直接实施松弛的一致性模型(例如  XC)。 8后一种方法的主要好处是它
消除了  L2  的昂贵停顿;而是在遇到  FENCE  时在  SM  处写入  stall。
更多的优化是可能的,以进一
步减少停顿[30]。 然而,
时间相干性存在一些关键限制。

8时间一致性[30]强制执行  XC  的变体,
其中写入不是原子的。
Machine Translated by Google

224  10.  异构系统的一致性和连贯性

表  10.7:
一致性导向的时间一致性:
表10.3的时间线

/*  T1  和  T2(属于不同的  CTA)
分别映射到  SM1  和  SM2。  fl  ag  =  0  (lease  =  35),  data1  =  0  (lease  =  30),  
data2  =  0  (lease  =  20)缓存在SM2的L1中  */  时间SM1  1  St1发出写请求6
SM2 L2

data1=NEW  已写入,
尽管当前时间  <  
data1  的租约
(30);确认发送到SM1
GWCT=30

11  St1  完成,
停顿时间  ←30

12  St2  发出写请求  17
data2=NEW  已写入,
尽管当前时间  <  
data2  的租约
(20);确认发送到SM1
GWCT=20

22  St2  完成;  GWCT  (20)<  stall‑
time(30),
所以stall‑time不

23  FENCE  停顿线程
直到失速时间  (30)
31  St3  发出写请求  36
fl  ag=自当前时间写入的  SET  >  lease  
for  fl  ag  (35);
确认
发送到  SM1
40 标志  (35)  的  L1  租约已到期,
因此  
Ld1  发出读取请求

41  St3  完成  45
fl  ag=SET  读取;
获得新租约;
发送到  SM2  的  L1
50 ld1完成
51 data2  (20)  的  L1  租约已过期,

此  Ld1  发出读取请求

56 data2=新读取;
获得新租约;
发送
到  SM2  的  L1
57 ld2完成
Machine Translated by Google

10.1.  GPU  一致性和连贯性  225

表  10.8:
一致性导向的时间一致性:
L1  控制器(差异以粗体显示)

驱逐/
加载 店铺
到期
发送  GetV(t)  到  L2 发送写到  L2
我 记录。
来自  L2  的数据 记录。
来自  L2  的Write‑Ack+GWCT
/在 stall‑time←max(stall‑time,GWCT)发送  
WriteV(t)  到  L2  rec。
来自  L2  的Write‑Ack+GWCT
V  读命中 ‑/我

失速时间←最大(失速时间,
GWCT)

表  10.9:
一致性导向的时间一致性:
L2  控制器(差异以粗体显示)
L2:
得到V(t) 写 写V(t) L2:
驱逐
到期
发送  Fetch  到  Mem
发送  Fetch  到  Mem
记录。
来自内存的数据
记录。
内存写入数据
我 发送数据到  L1
向  L1  发送  Write‑Ack
TS←t
/Exp
/P
摊位
如果(t  =  TS)
(直到到期) /Exp
发送数据到  L1 摊位 写

P TS←max(TS,t) 写 向  L1  发送  Write‑Ack

/S 向  L1  发送  Write‑Ack+GWCT 别的

停滞(直到到期)

摊位 摊位
发送数据到  L1 摊位
小号 写 写 /Exp
TS←max(TS,t) (直到到期)

向  L1  发送  Write‑Ack+GWCT 送  Write‑Ack+GWCT  到  L1

if(dirty)  
发送数据到  L1
写 写 发送回写到  Mem
指数 TS←t
向  L1  发送  Write‑Ack 向  L1  发送  Write‑Ack 记录。
来自内存的确认
/P
/我
Machine Translated by Google

226  10.异构系统的一致性和连贯性

‧  支持非包容性二级缓存很麻烦。 这是因为时间一致性要求在一个或多个  L1  中有效的块必须在  
L2  上具有可用的租用时间。
一个复杂的解决方法是可能的,其中一个未过期的块可以从  L2  中被
逐出, 前提是被逐出的块的租约被保存在某个地方,例如连同  L2未命中状态保持寄存器
(MSHR)  [30]。

‧  它需要全局时间戳。
由于现代  GPU  的面积相对较大,
维护全局同步的时间戳可能很困难。
然而,
最近的一项提案显示了如何在不使用全局时间戳的情况下实现时间相干性的变体[25]。

‧  性能可能对租赁期的选择很敏感。
租期太短会增加  L1  未命中率;
太长的租用期会导致写入(或  
FENCE)
停滞更多。

‧  时间一致性不能直接利用作用域同步。
例如, 涉及  CTA  范围同步的存储(直观上) 不需要写入  
L2 但在时间一致性中,每个存储都写入  L2,
因为它是在  write‑through/no‑write‑allocate  
的假设下设计的一级缓存。

‧  时间一致性涉及时间戳。
时间戳在设计和验证过程中引入了复杂性(例如,
时间戳翻转)。

总而言之,
尽管上述大多数限制都有变通办法,
但它们确实会增加已经非常规的基于时间戳的一致性
协议的复杂性。

10.1.4  发布一致性导向的一致性
基于强大的租约思想的时间一致性具有足够的通用性, 可以强制执行与一致性无关和以一致性为导向
的一致性变体。
另一方面, 涉及租约和时间戳的协议可以说是麻烦的。 在本节中,
我们讨论了  GPGPU  
一致性的替代方法,
称为发布一致性导向一致性(RCC), 它直接强制发布一致性  (RC),
它与  XC  的区别
在于区分获取和发布,而  XC  将所有同步视为相同。

RCC  在灵活性上有所妥协,因为它只能强制执行  RC  的变体。
但作为这种灵活性降低的回报,
RCC  可以说更简单, 可以自然地利用范围信息,并且可以与非包含的  L2  缓存一起工作。
在下文中, 我们
首先简要回顾  RC  内存模型, 然后使用作用域对其进行扩展。 然后我们描述了一个简单的  RCC  协议,

后进行了两次优化。 每个协议都可以强制执行  RC  的作用域和非作用域变体。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  227

发布一致性:
非作用域和作用域变体在本节中, 我们
从非作用域变体开始讨论  RC  内存模型,
然后使用作用域对其进行扩展。

回想一下  RC(在5.5.1  节中介绍)
有特殊的原子操作, 可以在一个方向上对内存访问进行排
序,
而不是  FENCE  强制执行的双向排序。 具体来说,
RC  有一个发布(Rel)存储和一个获取(Acq)加载,
它们强制执行以下顺序。

‧获取负载!
加载/存储
‧  加载/存储!
相对商店

‧  Rel  Store/Acq加载!
相对存储/采集加载

考虑表  10.10  中显示的消息传递示例。 将  St2  标记为发布可确保St1!St2;
将  Ld1  标记为获取
可确保Ld1!Ld2排序。  acquire9  (Ld1)
读取release(St2)写入的值。这样做时, 释放与获取同步,
确保全局内存顺序中的St2!Ld1 。 组合上述所有顺序意味着St1!Ld2, 从而确保  Ld2  看到新值而不
是  0。

表  10.10:
消息传递示例。
非范围  RC。

读T1 读T2 评论

St1:
St  数据  1  =  NEW; Ld1:
Acq  Ld  r1  =  fl  ag; //  最初所有变量都是  0。
St2:
Rel  St  标志  =  SET; B1:如果(r1≠SET) 转到Ld1;
Ld2:  Ld  r2  =  data1 r2==0可以吗?

在没有作用域的内存模型的变体中, 只要获取返回释放写入的值,
释放就会与获取同步, 而不
管它们所属的线程是来自相同作用域还是不同作用域。 因此,
在上面的示例中,
无论  T1  和  T2  属于相
同的  CTA  还是不同的  CTA,
Ld2  都会看到新值。

范围  RC  模型。
在作用域  RC  模型中,
每个原子操作都与一个作用域相关联。 如果出现以下情况,
则称发布与获取同
步:
(1)  获取加载返回由发布存储写入的值,以及  (2)  每个原子操作的范围包括执行其他操作的线程。

例如,
仅当执行获取和释放的两个线程属于同一  CTA  时, CTA  范围的释放才被称为与  CTA  范
围的获取同步。另一方面,
据说  GPU  范围的发布与  GPU  范围的获取同步, 无论

9Acquire是指加载标记为acquire,
release是指存储标记为release。
Machine Translated by Google

228  10.异构系统的一致性和连贯性

这两个线程是来自同一个  CTA  还是不同的  CTA(只要它们是在同一个  GPU  上发布的)。

表  10.11:
消息传递示例。
范围  RC。

读T1 读T2 评论

St1:
St  数据  1  =  NEW; Ld1:
CTA  Acq  Ld  r1  =  fl  ag; //  最初所有变量都是  0。
St2:  GPU  Rel  St  fl  ag  =  SET; B1:如果(r1≠SET) 转到Ld1;
Ld2:  Ld  r2  =  data1 r2==0可以吗?  (可以是  0,

果  T1  和  T2  来自不同的
号召性用语)

为了更直观, 请考虑表10.11中显示的消息传递示例的作用域变体。 正如我们所见, release  


St2  带有  GPU  范围,而  acquire  Ld1  仅带有  CTA  范围。 如果  T1  和  T2  来自不同的  CTA, 则获取  
(Ld1)  的范围不包括  T1。 因此, 在这种情况下, 可以说释放与获取不同步, 这意味着  r2  实际上可以读
取旧值  0。 另一方面, 如果  T1  和  T2  来自同一个  CTA, 则  r2  无法读取0。

形式化作用域  RC  的一种方法是使用  Shasha  和  Snir  的变体  for  malism  [28]并使用部分
顺序而不是全局内存顺序来对冲突操作进行排序。 更具体地说, 并不是所有的冲突操作都是有序的
只有相互同步的发布和获取是有序的。 值得注意的是, NVIDIA  采用这种方法来形式化其  PTX  内
存一致性模型[20]。

发布一致性导向一致性  (RCC)
在本节中,
我们介绍了一种直接执行  RC  而不是执行  SWMR  的协议。
具体来说,该协议不会急切地将写入传播到其他线程 即, 它不会强制执行  SWMR。 相反,
写入会在
释放时写入  L2,
并在另一个线程在获取时使  L1  自我无效并从  L2  中提取新值时变得对另一个线程
可见。

对于以下讨论,
让我们假设回写/写分配  L1  高速缓存。此外, 现在让我们忽略作用域并假设同
步发生在来自两个不同  CTA  的线程之间。
稍后我们将描述  RCC  如何有效地处理  CTA  内同步。  
RCC涉及的主要步骤如下。

‧  未标记为获取或释放的加载和存储在回写/写分配L1  高速缓存中的行为类似于正常加载和
存储。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  229

表  10.12:  RCC:
L1  控制器

加载/ 店铺/ Acq.scope  =  GPU/  Acq Rel.scope  =  GPU/   L1:

Acq.scope  =  CTA  相对范围  =  CTA (无范围) Rel  (no  scope)   驱逐

forall  dirty  blocks  
发送  GetV  到  L2 send  Write‑back  to  L2
记录。
来自  L2  的数据 记录。
确认
发送  GetV  到  L2 发送  GetV  到  L2
读/V 发送  GetV  到  L2
我 记录。
来自  L2  的数据 记录。
来自  L2  的数据
对于所有其他有效块 记录。
来自  L2  的数据
读/V 写/V
无效 写/V

发送回写到  L2

记录。
确认

forall  脏块发送回写到  
发送  GetV  到  L2
L2 if(dirty)  
记录。
来自  L2  的数据
记录。
确认 发送回写到  L2
V  读命中 写命中 读
写命中 记录。
来自  L2  的确认
对于所有其他有效块
发送回写到  L2 /我
无效
记录。
确认

表  10.13:  RCC:
L2  控制器

L2:
获取V 回写
驱逐
发送  Fetch  到  Mem  rec。 分配块
数据从  Mem  发送数据到   写

L1 /V 向  L1  发送  Ack
/在

if(dirty)  
写 发送回写到  Mem
V  向  L1  发送数据
向  L1  发送  Ack 记录。
来自内存的确认
/我

‧  在标记为释放的存储上, L1  中的所有脏块(由释放写入的块除外)
都写入L2。
然后,
将release写入的
block写入L2,
保证Load/Store !
相对商店。

‧  加载标记的获取从L2  读取块的新副本。 然后,
L1  中的所有有效块(acquire  读取的块除外)
都将自我失
效, 从而确保Acq  Load !
加载/存储。

例子。考虑表  10.10  中的消息传递示例,
假设  T1  和  T2  来自不同的  CTA,
映射到两个不同的  SM(SM1  和  
SM2)。
最初让我们假设
Machine Translated by Google

230  10.异构系统的一致性和连贯性

表  10.14:  RCC:
事件时间表(针对表10.10)

/*  T1  和  T2(属于不同的  CTA) 分别映射到  SM1  和  SM2。  fl  ag  =  0  and  data1  =  0  缓存在
SM1和SM2的L1中  */  Time  SM1/L1  L2  t1  St1写入data1=NEW完成t2  Rel  St2  
issues回写data1 SM2/L1

t3 data1=NEW  写入并确认
送回  SM1  的  L1
t4  Ack  收到;  fl  ag=SET  written及其
Write

返回启动
t5 fl  ag=SET  write  and  Ack  发回SM1的
L1
t6  Rel  St2  完成t7  t8
Acq  Ld1  issues  read  for  flag
fl  ag=SET  读取并发送到SM2的L1

t9 收到的价值;  data1  自失效;  Acq  
Ld1  完成
t10 读取  data1  的  Ld2  问题
t11 data1=NE  W  读取并发送到
SM2  的  L1
t12 ld2完成

包含data1和flag的缓存块缓存在SM1和SM2的L1中。 我们在表  10.14  中说明了事件的时间线。 在时间


=t1,
执行St1,导致NEW被写入缓存在SM1的L1中的data1。 在time=t2,  st2(marked  release) 导
致SM1的L1中的脏块全部写入L2。 因此, data1在time=t3写入L2。 然后, 在time=t4,  release  write
为执行, 导致  SET  被写入缓存在  SM1  的  L1  中的标志, 随后在时间  =  t5  时写回  L2。在  time=t7,  Ld1
(标记为  acquire) 发出一个  read  for  flag,
导致从  L2  读取  flag=SET。
在时间=t9, 当接收到该值时,
SM2的L1中的所有有效块, 除了acquire读取的块外, 都自我失效。

因此,
data1  是自我无效的。
在时间=t10,  Ld2  导致将对  data1  的读取请求发送到  L2,
并读取  NEW  
的最新值。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  231

利用范围。
如果所有原子操作都在  GPU  范围内, 则上述协议按原样适用。 这是因为协议已经在释
放时将所有脏数据推送到  L2(与  GPU  范围对应的缓存级别), 并在获取时从  L2  拉取数据。 值
得注意的是,RCC  协议可以利用  CTA  范围:
CTA  范围的发布不需要将脏块写回到  L2;  CTA  范
围内的获取不需要使  L1  中的任何有效块自行失效。

协议规范。 我们在表10.12和10.13中提供了  L1  和  L2  控制器的详细规格。 有两种稳定状态:


(I)nvalid  和  V(alid)。  L1  使用GetV和回写请求与  L2  通信。  GetV  请求将指定块带入  Valid  状
态的  L1。 与时间相干性中使用的GetV(t)不同, GetV不携带时间戳作为参数; 通过  GetV  进入  L1  
的块在获取时自动失效。 回写请求将指定的块从  L1  复制到  L2, 而不从  L1  逐出该块。

表10.12显示了  L1  控制器。
如果释放和获取属于  GPU  范围(或者如果释放和获取不携带
范围信息), 则协议必须在释放时写回所有脏块, 并在获取时自行使所有有效块无效。 释放和获
取  CTA  范围的行为类似于回写/写分配缓存中的普通存储和加载。

最后,值得注意的是该协议不依赖于缓存包含。 事实上,
如表  10.13  所示,
一个有效的  L2  块
可以在不通知  L1  的情况下被悄悄驱逐。
直觉上,这是因为  L2  不包含任何关键元数据,
例如共享者或所有权信息。

概括。综上所述, RCC  是一种直接强制执行  RC  的简单协议。因为它不在  L2  上保存任何协议元数
据,
所以它不要求  L2  具有包容性, 并允许  L2  块被静默驱逐。
该协议可以利用范围信息 具体来
说,
如果发布和获取属于  CTA  范围, 则不需要昂贵的回写或自我失效。 另一方面, 在不知道范围的
情况下,CTA  内同步效率低下: RCC  必须保守地假设释放和获取属于  GPU  范围, 并且从  L1  回写/
自我无效数据, 即使同步是在一个  CTA  内.

利用所有权:
RCC‑O释放(导致
所有脏行被写回  L2)
和获取(导致所有有效行自我失效) 是上述简单协议中的昂贵操作。降低发
布和获取成本的一种方法是跟踪所有权[13、16 ],
这样拥有的块就不需要在获取时自我失效,也不
需要在发布时回写。

关键思想是添加一个  O(wned)  状态 每个商店都必须在后续发布之前获得  L1  缓存行的
所有权。对于每个块,
L2  维护所有者, 它指的是拥有该块的  L1  的身份。
根据所有权请求,L2  首先
降级以前的所有者(如果有的话), 导致以前的所有者写回脏数据
Machine Translated by Google

232  10.异构系统的一致性和连贯性

表  10.15:  RCC‑O:
L1  控制器。
在非作用域版本中,
scope=GPU。

加载/ 店铺/ Acq.scope  =  GPU/   Rel.scope  =  GPU/   L1: 从  L2:

Acq.scope  =  CTA  相对范围  =  CTA Acq(无作用域)
发 Rel(无范围) 驱逐 请求回写
送  GetV  到  L2
发送  GetV  到  L2 发送  GetO  到  L2 记录。
来自  L2  的数据 发送  GetO  到  L2

我 记录。
来自  L2  rec  的数据。
来自  L2  的数据 读/V 记录。
来自  L2  的数据

读/V 写/O 对于所有其他有效的非  O  块  write/O

无效
发送  GetV  到  L2

发送  GetO  到  L2 记录。
来自  L2  的数据 发送  GetO  到  L2

V  读命中 记录。
从  L2  读取的数据 记录。
来自  L2  的数据 /我

写/O 对于所有其他有效的非  O  块  write/O

无效
发送回写到  L2  发送数据到  L2/
O读命中 写命中 读命中 写命中 V
记录。
来自  L2/I  的确认

到  L2。
将当前数据发送给新所有者后,L2  更改所有权。
因为处于状态  O  的块意味着不存在远
程写入器, 所以不需要在获取时使拥有的块自我无效。 因为  L2  跟踪所有者,
所以不需要在发
布时写回拥有的块。

所有权跟踪还有另一个好处: 即使在没有范围信息的情况下, 所有权跟踪也有助于降


低  CTA  内部收购的成本。考虑表  10.10  中显示的消息传递示例, 现在假设线程  T1  和  T2  是
同一个  CTA  的长线程。
在没有范围信息的情况下, 回想一下,RCC  将不得不保守地处理释放
和获取, 在释放时写回所有脏数据, 并在获取时使所有有效块自失效。 对于  RCC‑O,
仍然需要
保守地对待发布; 发布前的所有商店仍需获得所有权。 如果获取的块处于  Owned  状态, 则意
味着相应的释放必须来自同一  CTA  中的线程。 因此,
获取可以像  CTA  范围的获取一样对待,
并且可以避免有效块的自我失效。

协议规范。 我们在表10.15中给出了  L1  控制器的详细规范, 在表  10.16  中给出了  L2  控制器的


详细规范。 主要变化是增加了  O  状态。 每个处于  V(或  I)
状态的商店都必须联系  L2  控制器
并请求所有权。 获得所有权后, 该行的任何后续存储都可以简单地更新  L1  中的行, 而无需联
系  L2。
因此,处于  O  状态的块在  L2  可能是陈旧的。 为此, 当  L2  收到一条已经处于  O  状态的
线路的所有权请求时, L2  首先请求之前的所有者回写该块, 将最新的数据发送给当前的请求
者, 然后更改所有权。
Machine Translated by Google

10.1.  GPU  一致性和连贯性  233

表  10.16:  RCC‑O:
L2  控制器

获取V 得到O 回写 L2:


驱逐
发送  Fetch  到  Mem 发送  Fetch  到  Mem
分配块
记录。
来自内存的数据 记录。
来自内存的数据
我 写
发送数据到  L1 发送数据到  L1
向  L1/V  发送  Ack
/在 设置所有者/O

发送数据到  L1 写
V  向  L1  发送数据 /我
设置所有者/O 向  L1  发送  Ack

将  Req‑Write‑back  发送给  L1(所有者)
将  Req‑Write‑back  发送给  L1(所有者) 将  Req‑Write‑back  发送给  L1(所有者)
记录。
来自  L1(所有者)
的数据
记录。
来自  L1(所有者)
的数据 记录。
来自  L1(所有者)
的数据 写
欧 发送回写到内存
写/V 将数据发送到  L1(请求者)
更新所有 向  L1/V  发送  Ack
记录。
来自内存的确认
发送数据到  L1(请求者) 者/O
/我

在发布(CTA  范围、
GPU  范围或非范围)后,
无需将脏数据从  L1  写回到  L2。
相反,
该协议仅要求所有先
前的商店必须获得所有权。 在获取(GPU  范围或非范围)
时,只有非拥有的有效块需要自我失效。 如果获取是  
CTA  范围内的,
或者如果获取是针对已经拥有的块, 则意味着同步是  CTA  内的,
因此不需要自我失效。

最后,因为协议依赖于  L2  维护所有权信息,
所以  O  状态的  L2  块不能被静默驱逐;
相反,
它必须首先降级
当前所有者,
要求它写回有问题的块。

概括。
通过跟踪所有权, 与  RCC  相比,
RCC‑O  允许减少自我失效的数量。 在没有范围的情况下, 所有权跟踪还可
以检测内部  CTA  获取并避免在这种情况下进行自我失效的需要。 然而,
CTA  内的发布被保守地对待 每个存储
到以前无主的块仍然在后续发布之前获得所有权。 最后,RCC‑O  不允许状态  O  中的块从  L2  中静默驱逐;它必须
首先降级当前所有者。

Lazy  Release  Consistency‑directed  Coherence:  LRCC在
没有范围信息的情况下,
RCC  保守地对待发布和获取。
在  RCC‑O  中,
可以检测并有效处理  CTA  内获取,
但对释
放进行保守处理。

有没有一种方法可以有效地处理  CTA  内部发布?
延迟发布一致性导向一致性  (LRCC)  可以实现这一点。
在LRCC,
普通店铺不取得所有权。只有发布商店才能获得所有权, 代表程序顺序中它之前的商店。 当稍后获取释
放的块时, 块在获取时的状态决定了一致性操作。
Machine Translated by Google

234  10.异构系统的一致性和连贯性

如果该块由获取线程的  L1  拥有,则意味着同步是  intra‑CTA;因此, 不需要自我失效。 如
果该块由远程  L1  拥有,
则意味着同步是  CTA  间的。
在这种情况下, 远程  L1  中的脏缓存块首
先被写回,并且获取  L1  中的有效块自我失效。

因此,
通过延迟一致性操作直到获取,
LRCC  能够非常有效地处理  CTA  内同步。

表  10.17:  LRCC:
事件时间线(针对表10.10),  CTA  间同步

/*  T1  和  T2(属于不同的  CTA) 分别映射到  SM1  和  SM2。  fl  ag  =  0  and  data1  =  0  缓存在
SM1和SM2的L1s  */  SM1/L1  SM2/L1  L2
时间
t1  St1  写入数据  1=NEW
并完成t2  Rel  St2  
发布标志的所有权请求

t3 获得旗帜的所有权;
并发送到SM1

收到t4所有权;  fl  ag=SET  写
入,
St2  完成

t5   Acq  Ld1  issues  read  for  flag
t6 发给  SM1  的  L1  的  fl  ag  回写请
求(因为它拥有  fl  ag)

t7  (在用  fl  ag  响应之前)
数据  1  的回
写已启动

t8 data1=NEW  写入并确认
发送到  SM1  的  L1
t9收到确认;
标志=设置
发送到  L2
t10 标志值写入并发送到
SM2  的  L1
t11 Acq  Ld1  完成
t12 读取  data1  的  Ld2  问题
t13 data1=新读取;
发送到  SM2  的  L1

t14 ld2完成
Machine Translated by Google

10.1.  GPU  一致性和连贯性  235

示例: CTA  间同步。 考虑表10.10  中显示的消息传递示例, 假设线程  T1  和  T2  属于不同的  CTA。 最初,


让我们假设包含  data1  和  flag  的缓存块缓存在  SM1  和  SM2  的  L1  中。 我们在表  10.17  中说明了事
件的时间线。 在时间=t1, 执行  St1  导致  NEW  被写入缓存在  SM1  的  L1  中的  data1,
而没有获得所有
权。 在  time=t2, 执行  St2(标记释放), 导致将标志的所有权请求发送到  L2。 对于任何  SM  之前不
拥有的标志, L2  通过简单地将所有者设置为  SM1  并响应  SM1  来授予所有权。 收到响应后, 将  SET  写
入  SM1  的  L1  标志,St2  在时间  =  t4  完成。
在时间=t5,  Ld1(标记获取) 被执行, 导致对标志的读取
请求被发送到L2。 在L2, 发现包含flag的块为SM1的L1所有; 因此, L2请求SM1的L1回写标志。 在  
time=t7  收到此请求后,  SM1  的  L1  首先写回所有脏的非拥有块(包括  data1=NEW ); 然后, 在  
time=t9, 它写回  flag=SET。 在  time=t10,  L2  收到标志,将其写入本地并将值转发给  SM2。 在  
time=t11,  SM2  收到  flag=SET  并且  Ld1  完成。 最后,在  time=t12,  Ld2  导致向  L2  发送对  
data1  的读取请求, 并读取  NEW  的最新值。

表  10.18:  LRCC:
事件时间线(针对表10.10),  CTA  内同步

/*  T1  和  T2  属于相同的  CTA,
映射到  SM1。  fl  ag=0和data1=0缓存在SM1的L1
中。  */  T1  (SM1/L1)
时间 T2  (SM1/L1)  t1   L2
St1  写入  data1=NEW  并完成t2  Rel  St2  发出所有权请求

t3 获得标志的所有权并发送给  
SM1
收到t4所有权;  fl  ag=SET  
写入,
St2  完成

t5 Acq  Ld1  从中读取  fl  ag=SET
L1  并完成
t6 Ld2  从中读取  data1=NEW

L1  并完成

示例:CTA  内同步。
让我们考虑表10.10中显示的消息传递示例, 现在假设线程  T1  和  T2  属于同一个  
CTA,
因此映射到同一个  SM,
SM1。
我们在表  10.18  中说明了事件的时间线。
在时间=t1,  St1
Machine Translated by Google

236  10.异构系统的一致性和连贯性

被执行, 导致NEW被写入缓存在L1中的data1。 在  time=t2,


执行  St2(标记释放), 导致将标志的所
有权请求发送到  L2。 在获得所有权后, SET  被写入缓存在  L1  中的标志。 在时间=t5,
执行Ld1(标记获
取)。 由于  flag  归  SM1  所有,
因此不存在自我失效并且  flag=SET  只是从  L1  读取。 在时间=t6,
执行
Ld2,
从L1读取data1=NEW。

协议规范。 我们在表10.19  中给出了  L1  控制器的详细规范。  L2  控制器与  RCC‑O  的相同(表
10.16)。 与  RCC‑O  不同, 并非所有商店都获得所有权; 只有非范围/GPU  范围的版本获得自己的权
利。 与  RCC‑O  一样, CTA  范围内的获取或对  L1  拥有的块的获取意味着  CTA  内同步; 因此,
不存在自我
失效。 相反, 对于不属于  L1  的块的  GPU  作用域/非作用域获取意味着  CTA  间同步并涉及自我失效。 具
体的, 向L2发送GetV请求, 获取获取到的块的最新值。  (如果在  L2  上, 请求的块由不同的  L1  拥有,
则  L2  会导致  L1  在写回请求的块之前写回其所有脏的、 非拥有的块。) 那么所有有效的非拥有的  L1  
块,  除了  acquire  刚刚读取的块外, 都是自失效的。 最后,与  RCC‑O  一样,
LRCC  依赖于维护所有权信
息的  L2。 因此, 处于状态  O  的  L2  块不能被静默驱逐; 相反,它必须降级当前所有者, 要求它不仅写回有
问题的块, 而且还写回该  L1  中的任何其他非拥有的脏块。

表  10.19:  LRCC:
L1  控制器。
在非范围版本中,
范围设置为  GPU。

加载/ 店铺/ Acq.scope  =  GPU/   Rel.scope  =  GPU/   L1: 从  L2:

Acq.scope  =  CTA  相对范围  =  CTA Acq(无范围) Rel(无范围) 驱逐 请求回写


发送  GetV  到  L2  发送  
GetV  到  L2  发送  GetV  到  据。
L2  来自  
rec.来自  
L2  读
L2  
/V  
I  读
rec.  
/V  的
写数据。
/V  
O  的
块所有其他有效非  
来自  
的数据
L2  rec  的数 将  GetO  发送到  L2  
rec。
来自  L2  写入/O  的
数据
使发送  GetV  
到  L2  rec  无效。
从  L2  
读取的所有其他有效非   发送  GetO  到  L2  发if(dirty)  
送回写
到  L2  rec。
来自  L2  rec  
V  读命中 写命中 O  块的数据 的数据。 来自  L2  写入/O /I  的确认

无效

对于所有其他脏非  O  块对于所有其他脏非  
来自  L2/V  的确认 O  块发送回写到  L2  发送回写到  L2  rec。
L2/V  rec.  的确认。 来自  

O读命中 写命中 读命中 写命中


发送回写到  L2  rec。
来自  L2/I  
发送数据到  L2/V
的确认

概括。 通过延迟一致性操作直到获取, LRCC  允许即使在没有范围信息的情况下也能有效地检测和处


理  CTA  内同步。
LRCC  不允许状态  O  中的块从  L2  中静默驱逐, 但这的影响仅限于同步对象,因为只有释放存储才能获
得所有权。
Machine Translated by Google

10.2.不仅仅是  GPU  的异质性  237发布一致性导向一
致性:总结在本节中,
我们讨论了发布一致性导向一致性的三种变体, 它们可用于强制范围
内和非范围内的发布一致性。
虽然所有变体都可以利用范围知识, 但  LRCC  即使在没有范
围的情况下也可以有效地处理  CTA  内同步。

鉴于  LRCC  即使在没有作用域的情况下也可以有效地处理同步, 我们是否可以摆脱
作用域内存一致性模型? 我们认为不完全是。虽然惰性有助于避免在  CTA  内同步的情况下
浪费回写,但它会使  CTA  间获取速度变慢。这是因为惰性迫使释放线程的  L1  中的所有回
写都在获取的关键路径上执行。 在两个设备之间(例如  CPU  和  GPU  之间)
同步的情况
下,
这种效果变得更加明显。

因此,
GPU  和异构内存模型是否必须涉及范围是一个复杂的可编程性和性能权衡。

10.2  不仅仅是  GPU  的异质性在本节中,
我们考虑如何在由  CPU、
GPU  和
其他加速器组成的多个多核设备之间公开全局共享内存接口的问题。

使问题具有挑战性的是, 每个设备都可能保证通过不同的一致性协议强制执行的不
同的一致性模型。
例如,CPU  可以选择使用强制  SWMR  的一致性不可知协议来强制执行
相对强的一致性模型,
例如  TSO。另一方面,
GPU  可以选择通过采用一致性导向的惰性释
放一致性协议来强制执行作用域  RC。

当两个或多个具有不同一致性模型的设备集成时,异构设备的最终一致性模型是什
么?如何编程?如何整合两个设备的一致性协议?学术界和工业界正在积极研究这些问题
中的大多数。在下一节中,我们试图更好地理解这些问题并勾勒出设计空间。

10.2.1  异构一致性模型
让我们首先了解组成两个不同的一致性模型的语义。
假设两个组件多核设备  A  和  B  集成
在一起,
因此它们共享内存 由此产生的一致性模型是什么?

一个看似直截了当的答案是将两个内存模型中较弱的一个声明为整体模型。 然而,
如果这两个内存模型无法比较,
如我们在第  5.8.1  节中看到的那样,
这个简单的解决方案
将不起作用。
即使其中一个模型包含另一个模型, 也可能有更有效的解决方案。

例如,
假设多核  A  强制  SC,
多核  B  满足  TSO,
那么由  A  和  B  组成的异构多核如何表
现?
Machine Translated by Google

238  10.异构系统的一致性和连贯性

图10.2显示了异构多核处理器的运行模型。直观地,来自  A  的线程的操作应该满
足  A  的内存排序规则(在我们的示例中是  SC),
而来自  B  的操作应该满足  B  的内存模
型排序规则(TSO)。

C1 C2 C3 C4

负载
负载

商店
商店

转变

记忆

图  10.2:
异构架构的语义,
其中核心  C1  和  C2  支持  SC,
而  C3  和  C4  支持  TSO。

考虑表  10.20  中  Dekker  的例子。  Ld1  和  Ld2  是否都可以读取零? 这确实是可能
的,因为  TSO  不强制执行St2!Ld2排序。 如表10.21所示, 一旦在  St2  和  Ld2  之间插入了  
FENCE  指令, 这就变得不可能了, 因为  FENCE  现在强制执行  St2 !Ld2排序。 但是请注
意,在  St1  和  Ld1  之间不需要  FENCE, 因为  SC  已经强制执行St1!Ld1。

表  10.20:  Dekker  的例子。
组合  SC  +  TSO  内存模型。
线程  T1  映射到  SC  内核,
而  T2  
映射到  TSO  内核。

读取  T1  (SC) 读取  T2  (TSO) 评论

St1:  St1  fl  ag1  =  NEW; St2:
St  flag2  =  NEW; //  最初所有变量都是  0。
Ld1:
Ld1  r1  =  in  ag2; ld2:
ld  r2  =  flag1; //  r1  和  r2  都可以读到  0  吗?
//  是的!

因此,由具有不同一致性模型的组件多核组成的异构共享内存架构会导致复合一
致性模型,
使得源自每个组件的内存操作满足该组件的内存排序规则。虽然这个概念看起
来很直观,
但据我们所知,这还没有正式化。
Machine Translated by Google

10.2.不仅仅是  GPU  的异质性  239
表  10.21:  Dekker  的示例,
在  TSO  侧插入了  FENCE。
组合  SC  +  TSO  内存模型。

读取  T1  (SC) 读取  T2  (TSO) 评论

St1:
St  fl  ag1  =  NEW; St2:
St  flag2  =  NEW; //  最初所有变量都是  0。
栅栏 //  r1  和  r2  都可以读到  0  吗?
Ld1:
Ld  r1  =  fl  ag2;
ld2:
ld  r2  =  flag1; //  不!

使用异构一致性模型进行编程我们如何对这种异构共享内
存架构进行编程,
其中并非所有设备都支持相同的一致性模型?正如我们在上面看到的,
一种方法是
简单地以复合一致性模型为目标。
然而,可以想象,
使用复合内存模型进行编程很快就会变得棘手,

其是在涉及非常不同的内存一致性模型时。

一种更有前途的方法是使用具有正式指定(范围) 同步原语的  HSA  或  OpenCL  等语言进行
编程。 回想一下, C11  是一种基于无数据争用的顺序一致性(“SC  for  DRF”) 的语言模型, 我们在第
5.4  节中看到过。
HSA  和  OpenCL  使用范围扩展了  SC  for  DRF  范例,称为无异质种族的顺序一致性(SC  for  
HRF)  [15]。
HRF  在概念上类似于我们在上一节中介绍的作用域  RC  模型, 但有一些不同之处。 首先,
HRF  
处于语言级别, 而作用域  RC  模型处于虚拟  ISA  级别。 其次, 虽然  HRF  不为具有竞争的程序提供任何
语义, 但作用域  RC  模型为此类代码提供语义。 因此, HRF  和范围  RC  之间的关系类似于基于  DRF  的
语言模型和  RC  之间的关系。

HRF  有两种变体: HRF‑direct  和  HRF‑indirect。
在  HRF‑direct  中,仅当同步操作(释放和获
取) 具有相同的精确范围时, 两个线程才能同步。 另一方面, HRF‑indirect  允许使用不同的范围进行传
递同步。 例如, 在  HRF‑indirect  中,
如果线程  T1  使用范围  S1  与  T2  同步, 随后  T2  使用范围  S2  与  
T3  同步,
则称  T1  与  T3  可传递同步。  HRF‑direct  不允许这种传递性, 可能对程序员有限制。 值得注
意的是, 范围  RC  模型, 如  HRF‑indirect, 允许传递同步。

语言级作用域同步原语被映射到硬件指令; 每个具有独特硬件一致性模型的设备都有单独的
映射。  Lustig  等人。  [21]提供了一个名为  ArMOR  的框架,
用于根据指定不同内存一致性模型的精
确方式通过算法推导出正确的映射。
Machine Translated by Google

240  10.异构系统的一致性和连贯性

异构编程的第三种方法是使用组件硬件内存一致性模型之一进行编程, 同时使用  
FENCE  指令和其他指令检测在每个其他组件中运行的代码, 以确保它们与所选内存模型
兼容。 同样, ArMOR  框架可用于在内存一致性模型之间进行转换。

10.2.2  异构一致性协议
考虑两个多核设备  A  和  B,每个设备都遵守通过不同的一致性协议强制执行的不同的一
致性模型。 正如我们在图  10.3  中看到的,
每个设备都有一个本地一致性协议,用于保持其
本地  L1  的一致性。我们如何将两个设备集成到一个异构共享内存机器中? 特别是,
我们如
何正确地将两个一致性协议拼接在一起? 正确性取决于异构机器是否满足复合一致性模
型,
即来自每个设备的内存操作必须满足设备一致性模型的内存排序规则。

我们首先讨论层次一致性, 其中两个设备内一致性协议通过更高级别的设备间一致
性协议拼接在一起。 然后我们讨论粗粒度一致性跟踪在用于多芯片  CPU‑GPU  系统时如
何帮助减轻层次一致性的带宽需求。 最后, 我们将总结一个简单的  CPU‑GPU  一致性方法,
其中由  CPU  缓存的块不会缓存在  GPU  中。

设备‑A 设备‑B

核 核 核 核

L1 ⋯⋯
L1 L1 ⋯⋯
L1

本地一致性控制器   局部一致性控制器  
L2 A L2 B
垫片 垫片

全局一致性控制器  +  可选  LLC

记忆

图  10.3:
通过层次一致性将两个或多个设备集成到一台异构机器中涉及通过全局一致性
协议将两个本地一致性协议组合在一起。
Machine Translated by Google

10.2.不仅仅是  GPU  的异质性  241
异构一致性的分层方法回想一下, 在第9.1.6  节中,
我们介
绍了用于集成两个独立但同类一致性协议的分层一致性。 同样的想法也可以扩展到支持  het  
erogeneous  coherence。

更具体地说,层次一致性的工作原理如下。
本地一致性控制器在接收到一致性请求后, 尝试在
设备内本地完成该请求。
无法在本地完全满足的请求 例如,与其他设备中存在的共享者的  GetM  请求 被转发到全局一致
性控制器,后者又将请求转发到其他设备的本地一致性控制器。在为转发的请求提供服务后, 该本地
控制器向全局一致性控制器做出响应,后者又将响应转发给请求者。

要完成这一切:
‧全局控制器必须设计有足够丰富的接口来处理由设备本地控制器发起的一致性请求。

‧  每个本地控制器必须用垫片进行扩展,
这些垫片不仅充当本地和全局一致性控制器接口之间
的翻译器, 而且还选择适当的全局一致性请求来进行,
并适当地解释全局一致性响应。

为了更好地理解全局一致性接口应该是什么样子,
以及垫片的任务,
让我们考虑以下场景。

场景  1:
一致性不可知  +  一致性不可知。
假设我们要将多核  CPU  与类似  CPU  的加速器连接起来。
让我们假设这两种设备都采用与一致性无
关的、 SWMR  强制执行的一致性协议; 然而,这两种设备实际使用的协议是不同的:
CPU  使用目录协
议,
而加速器使用总线侦听。 此外,让我们假设这两个协议通过具有粗粒度共享者列表的全局目录协
议缝合在一起, 维护缓存块是否存在于一个或多个设备中。

现在, 假设一个  CPU  核心对一个块执行存储, 该块在  CPU  和加速器之间全局共享。 如图10.4  


所示,  CPU  核心  1  将向本地目录发送一个  Upg(rade)  请求,
该本地目录不仅要使  CPU  内的共享
器失效, 而且  2  还要向全局目录发送一个  Upg  请求, 以使加速器中的共享器失效.接收到Upg后, 全局
目录3必须将Inv(alidation)请求转发给加速器的本地监听控制器。 本地侦听控制器必须将  Inv  请求
解释为  GetM, 因为  GetM  请求是使侦听协议中的共享者无效的请求。  4  本地侦听控制器然后会在
其本地总线上发出  GetM  以使共享者无效。

因此, 为了将一致性无关协议拼接在一起,
全局一致性控制器必须有一个接口来处理类似于
表  6.4  的请求和响应。
Machine Translated by Google

242  10.  异构系统的一致性和连贯性

1.更新 2.更新 3.Inv 4.  GetM加


中央处理器 Local  Global  Local  Directory  目录  Snoop  Controller   速器核心
核 8.  Ack  7.  Ack  6.  Ack
5.数据

图  10.4:
将两个与一致性无关的协议拼接在一起:
来自  CPU  内核的存储的一致性事务。

第6章此外,
垫片必须根据其接口向全局控制器发出请求,并根据本地控制器的接口解释来自全局
控制器的转发请求和响应。
例如,侦听控制器必须将来自全局目录的  Inv  请求解释为  GetM。

场景  2:一致性导向  +  一致性导向假设我们想通过充当全
局一致性控制器的共享  LLC  将  GPU  与另一个类似  GPU  的加速器连接起来。
让我们假设  GPU  
和加速器都使用发布一致性导向一致性协议的变体强制执行非范围发布一致性: GPU  使用  
LRCC  协议,
而加速器使用  RCC  协议。

1.发送GetV(标志) 2.  GetV(标志) 3.  Req‑Write‑back 4.  Req‑Write‑back


加速。 L2 全球的 L2 L1
核心  (T2) (加速) 有限责任公司 (显卡) (显卡)
8.数据(标志) 7.数据(标志) 6.回写(数据1/ 5.回写(数据1/
标志) 标志)

图  10.5:
将两个一致性导向协议拼接在一起。

考虑表  10.10  中显示的消息传递示例, 假设  T1  在  GPU  上,T2  在加速器上。让我们假设已
经执行了  St1  和  St2, 并且包含标志和数据的缓存行位于  GPU  的  L1  之一中, 标志处于  O(wned)  
状态。 我们在图10.5中显示了当  T2  在加速器中执行获取  Ld1  时必须生成的一致性事务序列。  
1  加速器将向其本地  L2  控制器发送一个  GetV  标志请求。 由于加速器的本地  L2  没有包含标志的
块, 2  它的垫片必须将  GetV  转发到全局  LLC  控制器。 全局  LLC  控制器在找到  GPU  拥有的标志
后, 3  必须向  GPU  的本地  L2  控制器发送请求以写回所有脏块。  GPU  的  L2  控制器将  4  将此请
求转发给拥有该块的  GPU  L1。  5  然后, GPU  L1  控制器会将所有脏块(包括标志和数据) 写回  
GPU  的  L2。  6  反过来, GPU  的  L2  必须将脏块写回  LLC, 这就是现在的一致性点。

然后,
7  全局  LLC  控制器必须响应加速器的  L2  控制器
Machine Translated by Google

10.2.不仅仅是  GPU  的异质性  243
包含标志的块。  8  最后,
加速器的  L2  将响应请求者,
从而完成请求。

因此,关于将一致性导向协议拼接在一起,
全局  LLC  控制器充当一致性点。
因此,
垫片必须将请
求(例如,回写)转发给  LLC。

场景  3:
一致性不可知  +  一致性导向。
假设我们要将  CPU  和  GPU  连接在一起。 让我们假设  CPU  使用与一致性无关的  MSI  目录协议强制
执行  SC;  GPU  使用一致性导向的  LRCC  协议强制执行非作用域  RC。此外,
假设我们希望通过嵌入
在全局共享的  LLC  中的全局目录将两者连接起来(图10.3)。 应该如何将两个一致性协议拼接在一
起?

考虑表  10.22  中显示的消息传递示例, 假设  T1  来自  CPU,
T2  来自  GPU。
最初,
让我们假设数
据和标志都缓存在  GPU  和  CPU  的  L1  中,
初始值为  0。

表  10.22:
来自  CPU  的  T1  遵守  SC。  GPU  的  T2  遵循非作用域  RC。

读取  T1(CPU) 读取  T2(GPU) 评论

St1:
St  数据  1  =  NEW; Ld1:
获取  Ld  r1  =  标志; //  最初所有变量都是  0。
St2:
St  标志  =  SET; B1:如果(r1≠SET) 转到Ld1;
Ld2:  Ld  r2  =  data1 r2==0可以吗?

当  CPU  执行  St1  时,
如图10.6  所示,
它会  1  向本地目录控制器发送对数据  1  的  Upg  请求,

本地目录控制器  2  必须将请求转发到全局目录。 重要的是, 尽管  GPU  缓存数据  1,
但全局目录不需要
将  Upg  转发给  GPU。 这是因为  GPU  上的  LRCC  协议不要求共享器在写入时失效, 因为共享器在获
取时会自行失效。

因此,
全局目录  3  可以简单地用  Ack  进行响应。
当在  CPU  中执行释放  (St2)  时,
它必须导致类似的一
致性事务序列。

1.Upg(数据1) 2.Upg(数据1)
中央处理器 当地的 全球的
核心  (T1) 目录 目录
4.确认 3.确认

图  10.6:
将一致性不可知协议与一致性导向协议拼接在一起:
来自  CPU  的  St1  的一致性事务。

当  GPU  执行从  T2  获取  Ld1  时,
如图10.7  所示,
它会  1  向  GPU  L2  控制器发送一个  GetV  for  
flag  2  必须转发请求
Machine Translated by Google

244  10.  异构系统的一致性和连贯性

到全球  LLC/目录。 全局目录, 一旦发现包含标志的块在CPU中处于M(odified)状态,


3必须向CPU
的目录发送Fwd‑GetS请求。  CPU  的目录必须  4  将该请求转发给包含处于状态  M  的块的  
CPU  L1。
然后  CPU  L1  将  5  响应包含标志的块,
该标志最终必须转发给请求者。

1.发送GetV(标志) 2.GetV(标志) 3.  Fwd‑GetS(标志) 4.  Fwd‑GetS(标志)


显卡 L2 全球目录有限责任公司  (CPU) L1
核心  (T2) (显卡) (中央处理器)

8.数据(标志) 7.数据(标志) 6.数据(标志) 5.数据(标志)

图  10.7:
将一致性无关协议与一致性导向协议拼接在一起:
从  GPU  获取  Ld1  的  Coherence  事
务。

让我们考虑相同的消息传递示例, 假设现在  T1  在  GPU  上,
T2  在  CPU  上,
如表10.23  所
示。
当  GPU  执行  St1  时,
它只是将  data1  写入其本地  L1。

表  10.23:
来自  GPU  的  T1  遵守非作用域  RC。
来自  CPU  的  T2  依附于  SC。

读取  T1(GPU) 读取  T2(CPU) 评论

St1:
St  数据  1  =  NEW; Ld1:
Ld  r1  =标志; //  最初所有变量都是  0。
St2:
Rel  St  标志  =  SET; B1:如果(r1≠SET) 转到Ld1;
Ld2:  Ld  r2  =  data1 r2==0可以吗?

当  GPU  执行释放  St2  时,
如图10.8  所示,  GPU  将  1  向  GPU  L2  控制器发出标志的  
GetO  请求, 后者又  2  必须将其转发到全局目录。 因为标志缓存在  CPU  中, 所以全局目录  3  必须
将  Inv  请求转发给  CPU  的本地目录控制器, 后者将在使缓存的标志副本无效后  6  做出响应。

1.  GetO(标志) 2.  GetO 3.投资 4.投资

显卡 L2 全球的 目录 L1
核心  (T1) (显卡) 有限责任公司 (中央处理器) (中央处理器)

8.数据(标志) 7.数据(标志) 6.确认 5.确认

图  10.8:
将一致性导向协议与一致性不可知协议拼接在一起:
来自  GPU  的  St1  的  Coherence  
事务。

当  CPU  执行  Ld1  时,
如图10.9  所示,  1  一个  GetS  标志请求将被发送到本地目录控制
器,
后者又必须将其转发到全局目录控制器  2
Machine Translated by Google

10.2.不仅仅是  GPU  的异质性  245
LLC/目录控制器。 全局目录控制器在发现包含标志的块为GPU所有时, 3必须向GPU的本地L2控
制器发送请求以写回所有脏块。 反过来, GPU  L2  控制器必须  4  从该  L1  请求写回所有脏块。  
GPU  L1  控制器在收到请求后, 将  5  将其脏块(包括数据  1  和标志) 写回到  GPU  L2  控制器。  
GPU  L2  控制器必须反过来  6  将这些块写回全局目录/LLC。 当  data1  的回写到达全局目录/LLC  
时,LLC  在发现该块在  CPU  中有共享者时, 必须  7  将对  data1  的  Inv  请求转发到  CPU  本地目
录,这反过来会使缓存的  L1  无效  8  data1  的副本并用  ack  响应。 当标志的回写到达全局目录/
LLC  时,它必须  9  将值转发到  CPU  的  L2,CPU  又将其  10  转发到  L1, 从而完成  Ld1  请求。

由于  data1  现在在  CPU  的  L1  中无效,
当  CPU  执行  Ld2  时,
它会从  LLC  中获得正确的  NEW  
值。

1.GetS(标志) 2.  获取 3.  Req‑Write‑back   4.  Req‑Write‑back


中央处理器 L2 Global  L2  LLC  (GPU) L1
核心  (T1) (中央处理器) (显卡)
8.  Inv(数据1) 7.  Inv(数据1) 6.回写(数据1/ 5.回写(数据1/
10.数据(标志) 9.数据(标志) 标志) 标志)

图  10.9:
将一致性导向协议与一致性不可知协议拼接在一起:
从  CPU  获取  Ld1  的  Coherence  
事务。

我们在场景  3  中考虑的示例阐明了全局一致性接口的行为方式。 由于  GPU  存储而产生的任
何一致性请求必须使该块的  CPU  共享器无效。 因此, 对于此类请求, 如果有任何共享者, 全局一致
性接口必须将失效转发给  CPU。 相比之下,
由于  CPU  存储导致的任何一致性请求不需要使  GPU  
共享器无效, 因为  GPU  负责在获取时自行使其缓存块无效。 换句话说,全局一致性接口必须区分  
CPU  共享器和  GPU  共享器。 实现这一点的一种方法是将两种类型的读取请求作为接口的一部
分:(1)  GetS,它请求一个块进行读取并要求目录跟踪该块, 以便该块可以在远程写入时失效;  (2)  
GetV  [10],
它请求一个块用于读取并负责使该块自我失效, 因此目录不需要跟踪该块。

概括。
层次一致性是集成异构一致性协议的优雅解决方案。 分层一致性要求:
(1)  一个全局一致性
协议,
其接口足够丰富以处理一系列一致性协议;  (2)  对原始一致性协议(垫片) 的扩展,
用于与
全局一致性协议接口。

是否存在可用于将任意两个一致性协议拼接在一起的通用一致性接口?
而第6章表6.4中
指定的接口可以处理任何类型
Machine Translated by Google

246  10.异构系统的一致性和连贯性

由于一致性无关协议, 它无法处理一致性导向的一致性协议。 具体来说, 一致性导向的一致性协议的


特点是读取请求, 其中使行无效的责任在于请求者本身。 与  GetS  不同,
GetV  非常适合此类读取请求。
值得注意的是, 学术界(Crossing  guard  [22]和  Spandex  [10])
和工业界(CCIX  [2]、
OpenCAPI  
[4]、
Gen‑Z  [3]、
和  AMBA  CHI  [1])。

减轻异构一致性的带宽需求层次一致性的一个问题 尤其是对于  
CPU‑GPU  多芯片系统 是芯片间一致性互连可能成为瓶颈。

顾名思义, 在多芯片系统中,CPU  和  GPU  位于不同的芯片中,因此  GPU  必须通过片外一致性互
连访问全局目录。
由于  GPU  工作负载通常会导致高缓存未命中率, 因此需要频繁访问全局目录, 这解释了为什么一致性
互连会变得饱和。
减轻这种带宽需求的一种方法[24]是采用粗粒度一致性跟踪[12], 其中在  GPU(以及  CPU)

地以粗粒度(例如, 页面大小粒度)跟踪一致性状态.当  GPU  发生未命中并且已知对应于该位置的页
面对  GPU  是私有的或只读的,则无需访问全局目录。 相反,可以通过高带宽总线直接从内存访问该块。

针对异构  CPU‑GPU  一致性的低复杂度解决方案CPU  和  GPU  并不总是由
同一供应商设计。 不幸的是, 层次一致性通常需要对  CPU  和  GPU  协议进行适度的扩展(垫片)。

在多芯片系统中实现  CPU‑GPU  一致性的一种简单方法是选择性  GPU  缓存[7]。 映射到  CPU  
内存的任何数据都不会缓存在  GPU  中。 此外,当前缓存在  CPU  中的来自  GPU  内存的任何数据也不
会缓存在  GPU  中。
这个简单的策略平凡地加强了一致性。 为了执行此策略, GPU  维护一个粗粒度的远
程目录,
该目录维护当前由  CPU  缓存的数据。

每当  CPU  访问  GPU  内存中的一个块时,该粗粒度区域就会被插入到远程目录中。  (如果  GPU  正
在缓存该行, 则该行被刷新。) 远程目录中存在的任何位置都不会缓存在  GPU  中。

不幸的是, 上述天真的方案可能会招致严重的损失, 因为必须从  CPU  检索任何缓存在  CPU  中


的位置。为了抵消成本, 已经提出了一些优化, 包括  GPU  请求合并(合并对  CPU  的多个请求) 和  CPU  
端缓存(用于  GPU  远程请求的特殊  CPU  端缓存) [7] 。
Machine Translated by Google

10.3.延伸阅读  247

10.3  延伸阅读

在本章中,
我们看到了如何使用一致性导向的一致性协议来保持  GPU  的一致性。
尽管大多数关于  CPU  一
致性的文献都使用一致性无关的定义,
但也有一些经典著作将一致性协议定位到一致性模型。

阿菲克等人。  [6]提出了惰性一致性, 它直接强制执行  SC  而无需满足  SWMR。  Lebeck  和  Wood  
提出了动态自失效[18], 这是第一个基于自失效的一致性协议, 它直接针对  SC  以及较弱的模型而不满足  
SWMR。  Kontothanassis  [17]提出了  CPU  处理器的延迟发布一致性, 这是为  GPU  提出的类似此类协
议的先驱。

多核的出现重新激发了人们对以一致性为导向的一致性协议的兴趣。  DeNovo  [13]表明, 针对  
DRF  模型的一致性可以导致更简单和可扩展的一致性协议。  VIPS  [26]提出了一种无目录的方法来直接
执行发布一致性, 依靠  TLB  来跟踪私有和只读数据。  TSO  CC  [14]和  Tardis  [31]是一致性导向的一致性
协议, 分别直接针对  TSO  和  SC。

许多关于  GPU  一致性的提议是对  CPU  一致性协议的改编。 时间一致性[30]是第一个通过调整库
一致性  [19]  提出  GPU  一致性的人, 这是一种基于时间戳的  CPU  一致性协议。  HRF  [15]将  DRF  扩展到
异构上下文, 并展示了如何强制执行范围一致性模型。 与此同时,
辛克莱等人。  [16,  29]为  GPU  改编了  
DeNovo 特别是通过获得存储的所有权 并表明非作用域内存模型的性能几乎与作用域一致性模型一
样好[16]。  Alsop  等人。  [9]通过为  GPU  调整惰性发布一致性改进了这一点。 最后, Ren  和  Lis  [25]为  
GPU  改编了  Tardis,并表明  SC  可以在  GPU  上有效执行。

我们尚未明确解决的一项挑战是加速器,
尤其是  GPU,
除了高速缓存之外,
通常还有程序员控制的
存储器,
称为暂存器。已经有大量工作着眼于将暂存器和缓存集成到全局地址空间中[11、
16、
27 ] 。

10.4  参考文献

[1]  AMBA  CHI  规范。  https://developer.arm.com/architectures/
系统架构/amba/amba‑5  246

[2]  CCIX  联盟。  https://www.ccixconsortium.com/  246

[3]  GenZ  联盟。  https://genzconsortium.org/  246

[4]  OpenCAPI  联盟。  https://opencapi.org/  246
Machine Translated by Google

248  10.异构系统的一致性和连贯性

[5]  TM  Aamodt、 WWL  Fung  和  TG  Rogers。
通用图形处理器架构。 计算机体系结构综合讲座。  
Morgan  &  Claypool  出版社, 2018  年。DOI:
10.2200/s00848ed1v01y201804cac044  212

[6]  Y.  Afek、GM  Brown  和  M.  Merritt。
惰性缓存。  ACM  编程语言和系统事务,  15(1):182–205,  
1993.  DOI:  10.1145/151646.151651  247

[7]  Agarwal  N、
Nellans  DW、
Ebrahimi  E、
Wenisch  TF、
Danskin  J  和  Keckler  SW。
选择性  GPU  缓存以消除  CPU‑GPU  硬件缓存一致性。 在IEEE高性能计算机体系结构  (HPCA)  
国际研讨会上,
西班牙巴塞罗那,
2016  年  3  月  12‑16  日。
DOI:
10.1109/hpca.2016.7446089  
246

[8]  J.  Alglave、
M.  Batty、
AF  Donaldson、
G.  Gopalakrishnan、
J.  Ketema、 D.  Poetzl、 T.  Sorensen  
和  J.  Wickerson。  GPU  并发性: 弱行为和编程假设。 在过程中。 第  20  届编程语言和操作系统架
构支持国际会议  (ASPLOS), 第577‑591  页,土耳其伊斯坦布尔, 2015  年  3  月  14‑18  日。 DOI:
10.1145/2775054.2694391  214

[9]  J.  Alsop、
MS  Orr、
BM  Beckmann  和  DA  Wood。  GPU  的延迟发布一致性。 在第  49  届年度  
IEEE/ACM  微架构国际研讨会(MICRO)  中, 第  26:1–26:13  页,
台湾台北,
2016  年  10  月  15–19  
日。 DOI:10.1109/mi  cro.2016.7783729  247

[10]  J.  Alsop、
MD  Sinclair  和  SV  Adve。
氨纶: 一种灵活的界面, 可实现高效的异质性连贯性。
在ISCA  
中,  2018.  DOI:  10.1109/isca.2018.00031  245,  246
[11]  L.  Alvarez、 L.  Vilanova、
M.  Moretó、
M.  Casas、 M.  González、
X.  Martorell、
N.  Navarro、 E.  
Ayguadé  和  M.  Valero。 用于在共享内存多核架构中透明管理暂存器内存的一致性协议。 在过程
中。 第  42  届计算机体系结构国际研讨会, 第  720‑732  页,俄勒冈州波特兰, 2015  年  6  月  13‑17  
日。 DOI: 10.1145/2749469.2750411  247

[12]  JF  Cantin、
JE  Smith、
MH  Lipasti、
A.  Moshovos  和  B.  Falsafi。
粗粒度相干跟踪:RegionScout  
和区域相干阵列。  IEEE微,  26(1):70–79,  2006.  DOI:  10.1109/mm.2006.8  246

[13]  B.  Choi、
R.  Komuravelli、 H.  Sung、R.  Smolinski、
N.  Honarmand、SV  Adve、 VS
Adve、 NP  Carter  和  C.  Chou。  DeNovo: 重新思考纪律并行的内存层次结构。 并行架构和编译
技术国际会议  (PACT), 第155‑166  页, 德克萨斯州加尔维斯顿, 2011  年  10  月  10‑14  日。
DOI  :  
10.1109/pact.2011.21  231、 247

[14]  M.  Elver  和  V.  Nagarajan。  TSO‑CC:
TSO  的一致性定向缓存一致性。
在第  20  届  IEEE  高性能
计算机体系结构  (HPCA)  国际研讨会上,
Machine Translated by Google

10.4.参考文献  249

第  165‑176  页,
奥兰多,
佛罗里达州,
2014  年  2  月  15‑19  日。
DOI:
10.1109/hpca.2014.6835927  247

[15]  DR  Hower、
BA  Hechtman、 BM  Beckmann、 BR  Gaster、 MD  Hill、
SK  Rein  hardt  和  DA  
Wood。 异构无竞争内存模型。 在编程语言和操作系统的架构支持(ASPLOS), 第  427–440  页, 犹
他州盐湖城, 2014  年  3  月  1–5  日。DOI  :  10.1145 /  2541940.2541981  213、 217、 239、247

[16]  R.  Komuravelli,  MD  Sinclair,  J.  Alsop,  M.  Huzaifa,  M.  Kotsifakou,  P.  Srivastava,  SV
Adve  和  VS  Adve。 藏起来: 有你的暂存器, 也把它缓存起来。 在过程中。 第  42  届计算机体系结构
国际研讨会, 第  707‑719  页, 俄勒冈州波特兰, 2015  年  6  月  13‑17  日。
DOI  : 10.1145/  
2872887.2750374  217、 231、 247

[17]  LI  Kontothanassis、 ML  Scott  和  R.  Bianchini。
硬件一致多处理器的延迟发布一致性。 在过程
中。 超级计算, 第  61  页,
加利福尼亚州圣地亚哥, 1995  年  12  月  4‑8  日。
DOI:
10.21236/
ada290062  247

[18]  AR  勒贝克和  DA  伍德。 动态自失效: 减少共享内存多处理器中的一致性开销。 在过程中。 第  22  届


年度国际计算机体系结构研讨会  (ISCA), 第48‑59  页,意大利圣玛格丽塔利古雷,
1995  年  6  月  
22‑24  日。
DOI  :
10.1109/isca.1995.524548  217、
247

[19]  M.  Lis、
KS  Shim、
MH  Cho  和  S.  Devadas。
多核时代的内存一致性。 在IEEE  第  29  届计算机设
计国际会议  (ICCD), 阿默斯特, 马萨诸塞州, 2011  年  10  月  9‑12  日。
DOI:10.1109/
iccd.2011.6081367  247

[20]  D.  Lustig、
S.  Sahasrabuddhe  和  O.  Giroux。  NVIDIA  PTX  内存一致性模型的正式分析。 在
过程中。 第  24  届编程语言和操作系统架构支持国际会议  (ASPLOS),  2019  年。 DOI:  
10.1145/3297858.3304043  228

[21]  D.  Lustig、C.  Trippel、 M.  Pellauer  和  M.  Martonosi。  ArMOR:
防御异构架构中的内存一致
性模型不匹配。 在过程中。 第  42  届计算机体系结构国际研讨会, 第  388‑400  页,
俄勒冈州波特
兰, 2015  年  6  月  13‑17  日。239  DOI: 10.1145/2749469.2750378

[22]  LE  Olson、MD  Hill  和  DA  Wood。
交叉守卫: 调解主机加速器一致性交互。 在过程中。 第  22  届编
程语言和操作系统架构支持国际会议  (ASPLOS), 第163‑176  页,
中国西安,
2017  年  4  月  8‑12  
日。 DOI:10.1145/3093336.3037715  246
Machine Translated by Google

250  10.  异构系统的一致性和连贯性

[23]  MS  Orr、
S.  Che、
A.  Yilmazer、
BM  Beckmann、
MD  Hill  和  DA  Wood。
使用远程范围提升的同步。
在过程中。
第  22  
届编程语言和操作系统架构支持国际会议  (ASPLOS),
第73‑86  页,
土耳其伊斯坦布尔,
2015  年  3  月  14‑18  日。
DOI:
10.1145/2694344.2694350  217

[24]  J.  Power、
A.  Basu、
J.  Gu、
S.  Puthoor、
BM  Beckmann、
MD  Hill、
SK  Reinhardt  和  DA  Wood。
集成  CPU‑GPU  系
统的异构系统一致性。
在第  46  届年度  IEEE/ACM  微架构国际研讨会  (MICRO‑46),
第457‑467  页,
戴维斯,
加利福
尼亚州,
2013  年  12  月  7‑11  日。
DOI:
10.1145/2540708.2540747  246

[25]  X.  Ren  和  M.  Lis.通过相对论缓存一致性实现  GPU  中的高效顺序一致性。
在IEEE  高性能计算机体系结构(HPCA)  
国际研讨会上,
第  625‑636  页,
德克萨斯州奥斯汀,
2017  年  2  月  4‑8  日。
DOI  :
10.1109/hpca.2017.40  226、
247

[26]  A.  Ros  和  S.  Kaxiras。
复杂性有效的多核一致性。
并行架构和编译技术国际会议  (PACT),
第241‑252  页,
明尼苏达
州明尼亚波利斯,
2012  年  9  月  19‑23  日。
DOI:
10.1145/2370816.2370853  247

[27]  YS  Shao、
SL  Xi、
V.  Srinivasan、
G.  Wei  和  DM  Brooks。
使用  gem5‑aladdin  共同设计加速器和  SOC  接口。
第49  
届  IEEE/ACM  国际微架构研讨会  (MICRO),
第  48:1–48:12  页,
台湾台北,
2016  年  10  月  15–19  日。
DOI:
10.1109/
micro.2016.7783751  247

[28]  DE  Shasha和M.  Snir。
高效且正确地执行共享内存的并行程序。  ACM  编程语言和系统汇刊,  10(2):282–312,  
1988。
DOI:10.1145/42190.42277  228

[29]  MD  Sinclair、
J.  Alsop  和  SV  Adve。
赶走老鼠:
异构系统上松弛原子的语义和评估。
在过程中。
第  44  届年度国际计算
机体系结构研讨会  (ISCA),
第  161‑174  页,
纽约,
纽约州,
ACM,
2017  年。

DOI:  10.1145/3079856.3080206  247

[30]  I.  Singh、
A.  Shriraman、
WWL  Fung、
M.  O Connor  和  TM  Aamodt。  GPU  架构的缓存一致性。
在第  19  届  
IEEE  高性能计算机体系结构  (HPCA)  国际研讨会上,
第  578‑590  页,
中国深圳,
2013  年  2  月  23‑27  日。

DOI:  10.1109/mm.2014.4  216,  217,  220,  223,  226,  247

[31]  X.  Yu  和  S.  Devadas。  Tardis:
分布式共享内存的时间旅行一致性算法。
并行架构和编译国际会议  (PACT),

227‑240  页,
加利福尼亚州旧金山,
2015  年  10  月  18‑21  日。
DOI:
10.1109/pact.2015.12  247
Machine Translated by Google

251

第十一章

指定和验证
内存一致性模型和缓存一致

到目前为止,
我们希望让您相信一致性模型和缓存一致性协议是复杂而微妙的。
在本章中,我们
讨论了严格指定一致性模型和一致性协议并探索它们允许的行为的方法。
我们还讨论了指定其
实现和验证其正确性的方法。

我们首先简要概述指定并发系统的两种方法 操作和公理化 重点关注如何使用这些


方法指定一致性模型和一致性协议(第  11.1  节)。
给定正式的一致性规范,
我们将讨论如何自
动探索其行为(第11.2  节)。
最后,
我们将提供验证实现的快速浏览, 涵盖正式方法和基于测试
的方法(第11.3  节)

11.1  规格

规范通过精确定义系统的一系列合法行为,充当系统用户与其实施者之间的契约。
规范回答了
以下问题:
系统允许的行为是什么?相反,
实现回答了以下问题:系统如何执行规定的行为?

我们所说的行为是什么意思? 系统通过一组动作与其用户(或环境) 交互:(1)


输入动作:
从用户到系统的动作;  (2)内部动作:系统内部发生的动作;  (3)输出动作:从系统到用户的动
作。
其中只有输入和输出动作是用户可观察的 内部动作不是。 因此,一系列(可观察的)输入和
输出动作定义了系统的行为。

有趣的是两类行为属性:安全性和活跃性。
安全属性断言坏事不应该发生 即,它指定了哪些可观察到的行为序列是合法的。对于
并发系统,由于不确定性,
通常有多个合法序列。然而,安全不足以完全指定一个系统。

考虑一个接受输入动作并简单地停止的系统;
虽然还没有展出
Machine Translated by Google

252  11.  指定和验证内存一致性模型

有什么不好的,这样的系统显然是没有用的。这就是为什么规范还包括活性,
它断言好事最终一定
会发生。
在我们解释如何正式指定行为之前,让我们考虑一下一致性模型和一致性协议的可观察
行为是什么。
一致性模型: 可观察的动作和行为内存一致性模型是软件
(用户)
和硬件(实施者) 之间的契约。给定包含每线程加载和存储序列的多线程程序,
内存模型
指定每个加载必须返回的值。 因此,
相关的输入动作是加载和存储(以及它们的相关参数,包括处
理器核心  ID、
地址和在存储的情况下要存储的值)。输出动作是响应负载而返回的值。
这些可观
察到的操作(存储、 加载和返回值)
的序列表示内存一致性模型的行为。

一致性协议:
可观察的动作和行为回想一下, 处理器核心管
道与一致性协议交互以共同执行所需的内存一致性模型。 因此, 一致性协议的“用户” 是通过两个
输入操作与其交互的管道: (1)  程序中每个加载的读取请求;  (2)  程序中每个存储的写入请求。 一
致性协议的输出动作是: (1)读取返回, 为每个读取请求返回一个值;  (2)  write‑return  只是对
写入的确认。  (管道必须知道写入何时完成。) 这些可观察到的动作的序列表示一致性协议的
外部可观察到的行为。

重要的是要注意一致性协议与一致性模型中可观察到的内容之间的区别。在前者中,
读请求
或写请求返回的瞬间是可以观察到的。然而,
对于一致性模型,加载或存储返回的瞬间是不可观察
的 只有为加载返回的值是可观察的。

接下来,
我们讨论指定系统行为的两种主要方法:操作和公理化。
在前者中,
系统使用抽象参
考实现来描述,而在后者中,
数学公理用于描述系统的行为。

11.1.1  操作规范
操作规范使用参考实现描述系统的行为, 通常以状态机的形式表示。
参考实现展示的行为 即输
入/输出动作的顺序 定义了系统所有合法行为的集合。 操作模型通常使用内部状态和内部动作
来约束系统的行为,从而确保安全。 运营模型的活力取决于状态变化最终必须发生的事实。通常,
活性是使用以时态逻辑[31]编写的数学公理在外部(在状态机规范之外)
表达的。
Machine Translated by Google

11.1.规范  253

在操作上指定一致性模型和一致性协议可以使用抽象实现在操
作上指定一致性模型。与它们的现实对应物一样,抽象实现通常同时具有处理器核心流
水线组件和内存系统组件。回想一下,与一致性协议具有相同接口的内存系统组件(即,
相同的一组可观察操作:读取请求、
读取返回、 写入请求、
写入返回)
指定了一致性协议。

在下文中,
我们将描述顺序一致性  (SC)  的两种操作模型。
两种模型都具有相同的流水线组件,
但它们的内存系统组件不同 前者指定了一个一致性无关的一致性协议, 而后者指定了一个一致性
导向的一致性协议。这将使我们能够在操作上描述两者之间的差异。

SC  操作规范  1:
有序流水线  +  原子内存操作  SC  规范的示例类似
于第3.6章(开关) 中描述的简单  SC  实现。
除了可观察的操作(存储、 加载和返回的值)
之外,
该模型
还使用内部状态(内存) 来限制加载可以读取的值。

操作规范的工作方式如下。  (假设每个内核都有一个程序计数器,
指向下一条要获取的指
令。)

Step  1:  Fetch:
非确定性选择其中一个处理器内核;
它获取下一条指令并将其插入本地指令队列。

第  2  步:
从管道发出。再次不确定地选择了一个核心;它从其指令队列中解码下一条指令。
如果是内存
指令,
流水线会发出读取请求(用于加载)
或写入请求(用于存储) 并阻塞。

第三步:
原子存储系统。收到读取请求后, 它从内存中读取位置并返回值;
收到写请求后,
内存系统将其
写入内存并以  ack  响应。

第  4  步:
返回管道:
管道在收到响应后解除阻塞:
ack(在存储的情况下)
或值(在加载的情况下)。

本规范产生的行为是程序顺序中的输入动作序列(在第  1  步加载/存储)
和输出动作(在第  4  
步为加载返回的值)。很容易看出它的行为满足  SC。
例如,
在表11.1中的消息传递示例中,
可以观察到
以下序列:

«S1,  S2,  L1:r1=SET,  L2:r2=NEW»

同样,
L1  重复一次或多次直到它看到  SET  值的其他几个序列也是可能的。
但是以下  SC  违规是观察
不到的:
Machine Translated by Google

254  11.  指定和验证内存一致性模型

表  11.1:
消息传递示例

核心C1 核心C2 评论

S1:
St  数据  =  NEW; L1:
Ld  r1  =标志; //  初始数据和标志为  0。
S2:
St  标志  =  SET; B1:
如果(r1≠SET)
转到L1; L1、
B1可能重复多次。
L2:
Ld  r2  =  数据;

«S1,  S2,  L1:r1=SET,  L2:r2=0»

SC  操作规范  2:有序流水线  +  缓冲内存现在让我们考虑另一种  SC  
操作模型, 其中原子内存现在由全局  FIFO  存储队列前端以形成缓冲内存系统。 存储队列中的每个条
目都包含地址、 要存储的值以及发出写入请求的内核的  ID。 缓冲内存系统使用两种类型的  ack  响应写
入请求: 一种  ack  表示请求已插入写入队列, 另一种  late‑ack  表示写入已写入内存。
更具体地说, 缓冲
存储器系统的工作方式如下。  (步骤  1  和  4  与之前的规范相同, 因此省略。)

第  2  步:
从管道发出。一个核心是非确定性选择的;它从其指令队列中解码下一条指令。如果是存储指
令,
流水线发出写请求并阻塞;如果它是加载指令,则流水线等待最新的写入被延迟确认,然
后发出读取请求并阻塞。

步骤  3 :
缓冲存储器系统。 收到写请求后,缓冲内存系统将其插入全局存储队列并使用  ack  响应管
道。  (当写入最终被写入内存时,延迟确认被发送到发起写入的处理器核心。) 在收到读取
请求后, 从内存中读取值并返回到管道。

尽管有缓冲,
但模型产生的行为满足  SC。
回到表11.1  所示的例子:

«S1,  S2,  L1:r1=SET,  L2:r2=0»
上面的  SC  违规是不可观察的,
因为全局存储队列是一个  FIFO,
因此将存储提交到内存而不会违反每
个内核的程序顺序。

Consistency‑agnostic  vs.  Consistency‑directed  coherence
回想一下, 在第2章中, 我们将一致性接口分为两类: consistency  agnostic  和  consistency‑
directed。
原子内存系统(规范  1  中的第  3  步) 指定了一种一致性不可知协议, 因为写入是同步发生的
而缓冲内存系统(第  3  步) 是一致性导向协议规范的一个示例, 因为写入
Machine Translated by Google

11.1.规格  255

是异步的。  (后者可能有更激进的规范[3]。)尽管这两种规范都与有序管道相结合以强制执行  
SC,
但这两种协议表现出不同的行为。 考虑表  11.2  中所示的外部可观察动作的顺序。

«写请求(X,
1),
写返回(X,
1),
读请求(X),
读返回(X,
0)»

表  11.2:
线性化与  SC:
虽然  SC  允许读取返回  0,
但线性化不允许。

时间 核心C1 核心C2

t0 写请求  (X,1)
t1 写回(X,1)→ok
t2 读取请求  (X)
t3 读返回(X)→0

尽管不违反  SC,
但在原子存储系统中无法观察到上述序列。 这是因为原子内存规范确保写入实际上
是在其调用(t0)和响应(t1)之间写入内存, 确保读取将返回  1  而不是  0。
但是,
缓冲内存规范允许此行
为因为当读取发生时, 对  X  的写入可以缓冲在全局队列中, 从而允许读取看到  0。

可以使用比  SC  更强的正确性条件(称为线性化性[16])对原子存储系统(或一致性不可知
的一致性)进行建模。 除了  SC  规则之外,
线性化要求写入必须在其调用和响应之间实时生效。 可线性
化是一个可组合的属性: 一个对象是可线性化的, 当且仅当其组件是可线性化的。
转化为我们的设置,
一个记忆系统是可线性化的, 当且仅当它的每个位置都是单独可线性化的。 因此,
可以在每个内存位
置的基础上指定一致性不可知的一致性, 即每个内存位置的线性化能力。这也解释了为什么可以使用
分布式目录等独立地强制执行各个位置的一致性。

相反, SC  不是可组合的属性。即使所有内存位置都独立满足  SC(并且流水线不对内存操作
进行重新排序), 也不意味着整个系统都是  SC。 回想一下,
在第2章(边栏) 中,
我们研究了类似一致
性的一致性定义, 其中一致性被定义为基于每个内存位置的  SC。 然而, 这种一致性的定义并没有完
全指定一致性协议。  (因为线性化能力强于  SC, 每个位置的  SC  不足以指定一致性不可知的一致
性。 因为  SC  不是组合的, 每个位置的  SC  也不够强以指定一致性导向的一致性。) 然而,
per‑内存位
置  SC  是一种有用的一致性安全检查。 任何与一致性无关的协议 回想一下它满足
Machine Translated by Google

256  11.  指定和验证内存一致性模型

逐内存位置线性化 必然满足逐内存位置  SC 。
即使对于一致性导向的一致性协议, 每个位置的  SC  
也是一种有用的安全检查, 因为大多数一致性模型在每个位置的基础上明确要求  SC(并将其称为
“一致性”公理!
[4]) 。

总之,
与一致性无关的一致性公开了一个原子(可线性化)
内存接口,
而一致性导向的一致性
公开了目标一致性模型的接口。

在操作上指定实现到现在为止,
我们看到了
如何在操作上指定一致性模型和一致性协议。
操作方法的好处之一是较低级别的实现(例如,
详细
的一致性协议)
可以通过使用特定于实现的内部状态和操作改进基本规范来自然地建模。

例如,让我们将缓存添加到上述  SC  操作规范  (Spec1)  中,
每个处理器现在都拥有一个本地
缓存及其关联的一致性控制器。 来自管道的每个读/写请求现在首先发送到检查命中或未命中的本
地缓存控制器。 如果未命中,缓存控制器会通过  GetS  或  GetM  请求联系目录/内存控制器(正如我
们在前面关于一致性协议的章节中看到的)。 确切的状态转换自然取决于所采用的一致性协议的细
节,
但这里的要点是, 前面在表格格式中讨论的一致性协议本质上是通过改进内存系统(步骤  3  或
步骤  3 )
获得的操作模型。 同样,可以改进流水线规范来模拟额外的流水线阶段, 从而忠实地模拟
流水线实现。

工具支持操作
模型可以直接用任何表达状态机的语言表达, 例如  Murphi  (http://mclab.di.uniroma1.it/site/
index.php/software/18‑cmurphi)或  TLA  (https://  lamport.azurewebsites.net/tla/
tla.html)。特别是, 书中的每个一致性协议表都可以很容易地表示为状态机。

11.1.2  公理化说明
公理规范是一种更抽象的指定并发系统行为的方法,
其中由可观察的动作(以及这些动作之间的
关系)
组成的数学公理用于约束允许的行为集。

公理化地指定一致性模型我们在前面的章节
中用来描述  SC、
TSO  和  XC  内存模型的形式主义对应于公理化方法。
回想一下,
形式主义由以下组
件组成。
Machine Translated by Google

11.1.规范  257

‧可观察的动作:
这些是加载、
存储和为加载返回的值。

‧关系:
程序顺序关系定义为每个核心的总顺序,表示加载和存储在每个核心中出现的顺序。
全局
内存顺序定义为所有内核的内存操作的总顺序。

‧公理:
有两类公理 安全性和活性 分别指定安全性和活性属性。 对于  SC,
存在三个安全公
理:  (1)  保留程序顺序公理 全局内存顺序尊重每个内核的程序顺序;  (2)  加载值公理 加
载获取全局内存顺序中最近存储在它之前写入的值;  (3)  原子性公理 RMW  指令的加载和
存储以全局内存顺序连续发生。 除了这些安全公理之外,还有一个活性公理, 它指出任何内存
操作之前都必须有无限序列的其他内存操作; 通俗地说,
这意味着内存操作最终必须执行, 不
能无限期地延迟。

对于任何可观察到的动作序列 如果我们能够构建一个全局记忆顺序
遵守上述公理,则称其为内存模型的合法行为。

公理化地指定一致性协议我们之前看到了如何
在操作上指定一致性协议。 在这里,
我们看到了如何在公理化的更抽象的层次上指定它们, 这对于验
证目的很有用。 具体来说,我们专注于指定满足线性化的一致性无关的一致性协议。  (一致性导向
的一致性协议可以像公理化的一致性模型一样被指定。) 回想一下,
线性一致性比  SC  更强,
例如,它
不允许表11.2中所示的动作序列。因此,
规范在  SC  之上添加了一个附加公理来约束此类行为。

‧可观察的动作:
因为我们指定了一致性协议,相关动作是读请求、
读返回、
写请求和写返回事件
(处理器管道看到的一致性协议接口)。

‧内部动作:
除了可观察的动作之外, 我们还添加了两个内部动作 读取执行和写入执行 读取
或写入生效的瞬间。  ‧关系:
与SC  一样,
全局内存顺序被定义为所有内核的读取执行和写入
执行事件的总顺序。

‧公理:
除了与  SC  相关的三个安全公理之外, 还有第四个公理表明读取或写入必须在其调用和
响应之间执行。 更正式地说, 一个写‑执行(read‑perform)
动作必须出现在全局内存顺序中的
写请求(read‑request)
和写‑返回(read‑return)
动作之间。

最后,
就像在  SC  中一样,
有一个活性公理表明任何读取或写入请求最终都必须返回。
Machine Translated by Google

258  11.  指定和验证内存一致性模型

公理化地指定实现我们之前看到了如何通过
使用内部状态和动作扩展基本操作规范来自然地在操作上表达实现(例如, 一致性协议实现)。
以类
似的方式, 实现也可以通过扩展基本公理化规范来公理化地表达。 在本节中,
我们将重点关注如何按
照  Lustig  等人的公理化方式指定处理器核心流水线。  [20]。

回想一下公理规范的抽象本质,其中加载/存储被建模为单个瞬时动作。
不过,
我们现在的目标有
所不同。它不是指定正确性 相反,
目标是忠实地模拟一个真实的处理器内核,其中每个加载或存储都
经过多个流水线阶段。因此,
单个加载或存储操作现在扩展为多个内部管道子操作,每个子操作代表一
个管道阶段。

例如,让我们考虑一下经典的五级流水线。 在这里, 一个加载被分成五个子动作: 获取、解码、 执


行、
存储和写回。 存储分为七个操作:
获取、解码、
执行、 内存、 写回、 退出存储缓冲区和内存写入。  
(“Memory”
是指管道中的内存阶段,而“memory‑write”
是指将值实际写入内存的子动作。)

回想一下,公理一致性规范的一个关键组成部分是保留程序顺序  (ppo)  公理, 它要求程序顺序
必须保留在全局内存顺序中。 类似于  ppo,
其想法是使用流水线排序公理来指定微架构发生前 即,
指定流水线是否保留不同指令的子操作之间的顺序。 同样,理解  ppo  和流水线排序公理之间的细微差
别很重要。 前者的目标是指定正确性,而后者的目标是忠实地建模管道。 事实上,
管道实现是否遵守  
ppo  公理(对于预期的一致性模型) 是一个重要的验证问题, 我们将在下一节中解决。

对于五级流水线,
流水线排序公理如下。

‧获取荣誉计划订单。
如果指令i1在程序顺序  (po)  中出现在i2之前,
那么对i1的获取发生在i2  之

前。
即:  i1 !  i2  H)  i1:
取!  i2:
获取

‧解码正常。如果i1的获取发生在i2  之前, 则i1的解码发生在i2  之前。
那就是:  i1:fetch !  i2:
获取  H)  i1:
解码!我2:解码

‧  同样,
执行、
内存和写回阶段保留顺序(省略)。

‧  FIFO  存储缓冲区。如果存储i1的写回发生在存储i2  之前, 则i1在i2之前退出存储缓冲区。
那就是:  i1:writeback!  i  2:writeback  H)  i1:exits‑store‑buffer !  i2:exits‑store‑
buffer

‧有序写入。 如果i1在  i2之前退出存储缓冲区, 则i1在i2之前写入内存。 那就是:  i1:exits‑store‑


buffer !  i  2:exits‑store‑buffer  H)  i1:memory‑write !  i2:
内存写入。
Machine Translated by Google

11.1.规格  259

最后,
加载值公理指定加载可以从同一地址的存储中读取值的约束。

‧加载值公理。如果存储i1在加载  i2  的内存阶段之前写入内存(并且中间没有其他存储的其他内
存写入),则i2读取i1写入的值。

遵守上述公理的任何管道操作序列(和返回的加载值) 都是合法行为。为了更好地理解这一
点,
请考虑表11.3  中显示的消息传递示例。
当L1看到新值SET时,
L2能看到旧值0吗?

如果可以构建满足上述公理的管道操作的全局序列,则五级管道将允许此行为。
如果不是 即,
如果
约束导致循环,
则不允许该行为。让我们尝试构建一个全局序列。

‧  S1:
内存写入!  S2:memory‑write  (流水线排序公理确保  S1  在  S2  之前写入内存)。

‧  S2:
内存写入!  L1:
内存(S2必须在L1的内存之前写入内存
阶段 只有到那时,
L1  才能从  S2  读取)。
‧  L1:
内存!  L2:
内存(流水线排序公理确保  L1  在  L2  之前执行其内存阶段)。

‧  L2:
内存!  S1:memory‑write  (如果  L2  必须读取旧值,
而不是  S1  产生的值,
则其内存阶段
必须发生在  S1  的内存写入之前)。

表  11.3:
消息传递示例

核心C1 核心C2 评论

S1:
St  数据  =  NEW; L1:
Ld  r1  =标志; L1  读取  SET

S2:
St  标志  =  SET; L2:
Ld  r2  =  数据; L2能读到初始值0吗?

正如我们所看到的,上面列出的约束是不可能满足的,
因为它会导致循环。
因此,
对于这个例子,五级流水线不允许这种行为。
在上面的讨论中,
我们没有明确地对缓存或缓存一致性建模。  Man  erkar  等人。  [24]展示
了如何扩展管道的公理规范来模拟一致性协议/管道交互。

总之,
我们看到了如何公理化地指定管道实现。
建模实现的主要好处是可以验证它们。
在第  
11.3.1节中,
我们将看到如何根据其一致性规范验证实现的公理化模型。
Machine Translated by Google

260  11.  指定和验证内存一致性模型

工具支持。
规范语言, 例如Alloy  (http://alloy.mit.edu)或Cat,与Herd工具(http://diy.inria.fr/herd/)  相
关联,
可用于表达一致性模型的公理规范。 规范域特定语言允许以公理方式指定管道和一致性协
议实现(http://check.cs.princeton.edu/#tools)。

11.2  探索内存一致性模型的行为

因为一致性模型定义了共享内存行为,所以对于程序员和中间件编写者(例如, 编译器编写者和内
核开发人员)
而言,了解内存模型允许哪些行为以及不允许哪些行为是至关重要的。 通常,
内存一
致性模型由处理器供应商以散文形式指定, 这通常是模棱两可的[1]。
正式指定的内存一致性模型
不仅是明确的,而且还可以自动探索行为。
但在深入研究如何进行这种探索之前, 让我们简要讨论
一下⽯蕊试纸 揭示内存模型属性的简单程序。

11.2.1  ⽯蕊试验
内存一致性模型可能与使用库进行同步的高级程序员不太相关。 然而,它对于开发同步结构的低
级程序员来说很重要 无论是实现语言级同步的编译器编写者, 还是开发内核级同步的内核编写
者,
或者是开发无锁数据结构的应用程序程序员。 这样的低级程序员会想知道内存模型表现出的
行为,以便他们可以为他们手工制作的同步情况获得所需的行为。  Litmus  测试是抽象同步情况
的简单多线程代码序列;因此,
内存模型在这些测试中表现出的行为对低级程序员具有指导意义。
例如,它们可能会揭示是否需要特定的  FENCE  才能获得所需的行为。这些⽯蕊测试也用于处理器
供应商手册中,以解释内存一致性模型的属性。

例子。
回想一下, 我们在前面的章节中使用过这样的试金⽯来解释内存模型的行为。 例如,
表3.3被称为  
Dekker  的试金⽯,因为它抽象了  Dekker  互斥算法中的同步情况;表3.1通常称为消息传递测试。
考虑表11.4中显示的另一个例子, 它被称为“一致性” ⽯蕊试纸。
导致  r1  读取新值但  r2  读取旧值  
0  的执行违反了数据值不变性, 因此违反了一致性试金⽯。
Machine Translated by Google

11.2.探索内存一致性模型的行为  261

表  11.4:
当  r2  为  0  时  r1  可以是新的吗?

核心C1 核心C2 评论

S1:  x  =  新的; L1:  r1  =  x; //  初始  x  =  0


L2:  r2  =  x

11.2.2  探索
给定内存模型的正式规范和试金⽯,我们能否自动探索该试金⽯的内存模型的所有可能行为?
幸运的
是,
有工具可以做到这一点。直观地,一旦内存模型被形式化 公理化或操作化 就可以详尽地枚举
⽯蕊测试的每一个可能的时间表,
并且对于每个这样的执行,使用数学公理(在公理化的情况下)或执
行状态机(在操作的情况)
来确定执行的输出(负载返回的值)。

工具支持回想
一下,  herd工具提供了一种语言来以数学方式表达一致性模型; 该工具还有一个内置模拟器, 用于
探索⽯蕊试纸的行为。  ppcmem  工具(https://www.cl.cam.ac.uk/~pes20/ppcmem/help.html)
做类似的事情, 但专用于  POWER  和  ARM  内存模型, 而  CppMem  工具(http://svr  pes20‑
cppmem.cl.cam.ac.uk/cppmem/)适用于  C/C++  语言级内存模型。 就运行模型而言, RMEM工具
(https://www.cl.cam.ac.uk/~sf502/regressions/rmem/)内置了ARM(多变体)、 POWER(多
个变体)、 x86‑TSO  和  RISC‑V,
还允许探索它们的行为。

如何生成⽯蕊试纸? 它们当然可以手动生成以测试任何有趣的内存模型特性或同步情况。 还有
工具支持。
Litmus  测试可以通过测试生成器随机生成[14、 15 ]。  diy  工具(http://diy.inria.fr/doc/gen.html)
帮助生成⽯蕊测试给定测试的形状(程序顺序关系和共享内存依赖性)。 最后, litmusttestgen  
(https://github.com/nvlabs/litmustestgen)是一种自动化程度更高的方法, 可为公理化表达的内
存模型规范生成一组全面的⽯蕊测试。

虽然⽯蕊测试是传达关于内存模型的直觉的好方法, 但它们通常不能作为内存模型的完整规范,
因为它们留下了一些未指定的潜在行为(那些未包含在测试中的行为)。 但如果有足够数量的测试和
内存模型的句法模板(缺少部分),MemSynth  (http://memsynth.uwplse.org/ )展示了如何合成
满足⽯蕊测试的完整内存模型。
Machine Translated by Google

262  11.  指定和验证内存一致性模型

11.3  验证实施
内存一致性实施跨越多个子系统,
包括处理器管道以及缓存和内存子系统。
所有这些子系统必须相
互合作以强制执行承诺的内存一致性模型。
验证他们这样做是否正确是本节的主题。

最终目标是彻底验证完整的实现是否满足承诺的内存模型。
但是现代多处理器的绝对复杂性
使这成为一个非常困难的问题。因此,
存在各种验证技术,
从正式验证到非正式测试。
这本身就是一个
巨大的话题,可能值得单独写一本书。
在这里,
我们提供了其中一些技术的风格,
并提供了进一步研究
的指导。

11.3.1  正式方法

在这个类别中,
正式的实现模型根据规范进行验证。我们根据规范和实现是以公理方式还是以操作
方式表达来对这些方法进行分类。
然后,我们通过考虑属于该类别的一两个技术示例来解释每个类
别。
我们以关于操作方法与公理化方法的一些结束性思考来结束本节。

针对公理规范的操作实现
手动证明给定
以操作模型形式的实现和公理模型形式的规范,
可以通过手动证明证明该实现满足规范的每个公
理。

例如, 考虑一个系统, 其中处理器流水线按程序顺序呈现加载和存储, 以及确保  SWMR  和数据


值不变量的高速缓存一致性协议。
Meixner  和  Sorin  [26]表明,这样的系统通过证明它满足每个  SC  公理来强制执行  SC。

一个用于提出证明的强大模板[30]遵循  Lamport  的完全排序分布式系统事件的方法[18]。

个想法是为每个独立的动作分配假设的时间戳(例如, 在管道中获取存储指令) 并导出因果相关事件
的时间戳(例如,由于存储获得更高的时间戳而导致的  GetM  请求)。 在这样做的过程中,导出了所
有内存操作的全局顺序。然后检查每个加载的值以确定它是否返回与加载值公理一致的值。

模型检查一致性
协议的操作规范通常使用模型检查器在变体中针对公理化进行验证。
模型检查器,
如  Murphi  [12],
允许一致性协议类似于前面章节中以表格格式描述的协议,
以领域特定语言表示。
该语言还允许表
达  SWMR  等公理。
Machine Translated by Google

11.3.验证实施  263

然后模型检查器探索协议的整个状态空间,
以确保不变量成立或报告反例(违反不变量的状态序
列)。
然而,由于状态空间爆炸,
这种显式状态模型检查只能针对有限的实现模型(例如,
两个或三个
地址、
值和处理器)执行。

有许多方法可以对抗状态空间爆炸。
符号模型检查器[7]使用逻辑来操纵状态集(而不是单个
状态),从而实现模型的缩放。
有界模型检查器[6]通过仅在状态序列中寻找有限长度的反例来妥协
穷尽性。最后,
完整证明的一种方法是对有限系统进行详尽的模型检查, 然后使用参数化[11、
17 ]来
概括任意数量的处理器、地址等。

以下是一个非平凡的一致性协议如何建模的具体示例
使用  Murphi  检查:  (https://github.com/icsa‑caps/c3d‑protocol)

针对操作规范的操作实现假设有两个状态机:  Q  (实现)
和S  (规
范),具有相同的可观察操作集。
我们如何证明Q忠实地实现了S ? 为了证明这一点,
我们需要证明Q
的所有行为(可观察到的动作序列)都包含在S中。

精化一种允
许对Q和S  (而不是序列)
中的状态对进行推理的程式化证明技术称为精化[2]  (也称为模拟[27])。
关键思想是确定一个抽象函数F  (也称为细化映射或模拟关系),
它将实现的每个可达状态映射到
规范的状态, 这样:

‧  初始状态(q0  2  Q;  s0  2  S)
是映射的一部分。
即F(q0)  =  s0;

‧  抽象函数保留状态转换。
也就是说,
对于两个状态qi和qj之间的实现中的每个状态转换,
都有
A
一些可观察到的动作a,
即qi !  qj ,
抽象函数将导致规范中具有相同可观察动作的两个状态,
F .qi/ !  

F.qj /.
A

例如,考虑一个包含缓存和全局内存的缓存一致性系统, 该系统使用原子总线上的  MSI  一致
性协议保持一致性(实现)。 回想一下, 实现的整体状态包括所有缓存的状态(每个位置的值和  
MSI  状态)以及全局内存的状态。我们可以证明缓存一致性内存使用细化实现原子内存(规范)。 为
此,我们确定了一个抽象函数, 将缓存一致性内存的状态与原子内存的状态相关联。 具体来说, 给定位
置的原子内存块的值由下式给出:  (1)  如果该块处于  M  状态,
则该位置的缓存块的值;  (2)  否则为
Machine Translated by Google

264  11.  指定和验证内存一致性模型

与处于  S  状态和全局内存的每个块中的值相同。 然后证明将查看协议中所有可能的状态转换以检
查抽象是否
W  ri  
te.X;1/函数成立。
例如,考虑S !某些缓存块  X  的初始值为   0  的M转换。
对于进入  M  状态的缓存块, 抽
象函数成立, 因为它将缓存最新值  1。
对于处于状态的其他缓存块S, 如果  MSI  协议正确执行  
SWMR  并使所有这些块无效, 则抽象函数成立。

因此,
精化证明不仅需要识别抽象函数,
还需要对已实现系统中的状态转换进行后续推理;
该过程可能会揭示其他需要证明的不变量(例如,
上面揭示的  SMWR)。  Kami  框架[10]使用精
化证明来表明处理器的操作规范与高速缓存一致性内存系统相结合满足  SC。

模型检查表明实
现满足规范的一种方法是用相同的输入动作序列并排运行两个状态机, 并观察它们是否产生相同
的输出动作。
虽然这个简单的策略是合理的, 但它可能并不总是成功。  (当该方法说状态机等价
时,
它们肯定是等价的 但当它说它们不等价时, 它们可能仍然是等价的。) 这是因为并发系统固
有的不确定性 有时,规范和实现可能显示相同的行为,但具有不同的时间表。 话虽如此,
这是一个
非常有用的策略,尤其是当实现和规范在概念上相似时。银行等。  [5]采用这种策略来表明缓存一
致性协议满足承诺的一致性模型。

针对公理化规范的公理化实现我们看到了如何以公理化的方
式指定实现(例如核心管道实现)。
如何形式化验证这样一个公理化模型满足一致性模型的公
理化规范1 ?

Check  工作线[20、
21、
24 ]为此目的利用了详尽探索的想法。给定一个⽯蕊测试和内存模型
的公理化规范, 我们在11.2节中看到了如何在⽯蕊测试上详尽地探索内存模型的所有行为。 对于实
现的公理规范也可以进行这种详尽的探索。 然后可以将⽯蕊测试中的行为与抽象公理模型中的行
为进行比较。 如果实施显示出更多行为, 则表示实施中存在错误。另一方面,
如果实现显示的行为少
于规范,则表明该实现是保守的。 然后对作为套件一部分的其他⽯蕊测试重复相同的过程。

1  本节假设根据公理规范进行验证。验证公理化模型的类似方法
根据操作规范的实施是可能的, 尽管我们知道在这个类别中没有这样的提议。
Machine Translated by Google

11.3.验证实施  265

上述方法的一个局限性是验证仅针对探索的⽯蕊测试是详尽无遗的。 完整的验证需要探索
所有可能的程序。  PipeProof  [22]通过对程序的符号抽象进行归纳式证明来实现这一点,
该程序
允许对具有任意数量指令的程序以及所有可能的地址和值进行建模。

总结:
操作与公理一般来说,公理规范
是声明性的;
他们描述了哪些行为是允许的,
但没有完整描述系统是如何实现的。
通常,
它们更抽象,
更容易用数学表达;
因此,
可以更快地探索他们的行为。

另一方面,
操作模型更接近于实现,
对架构师来说更直观;因此,
可以说更容易在操作上对详细的实
现进行建模。
回想一下,
我们能够在前面的章节中以表格格式自然地描述相当复杂的一致性协议。
总而言之,
是走
公理路线还是操作路线,
这不是一个简单回答的问题。

最后,
重要的是要了解混合公理化/操作模型也是可能的。 例如,
让我们考虑一个使用一致性
协议保持高速缓存一致性的多处理器。 除了在操作上描述一致性协议,还可以使用  SWMR  和数据
值不变量公理化地抽象它。 当将一个复杂的验证问题分解成更简单的问题时, 这种方法可能很有吸
引力 例如,
当验证管道和内存系统的组合正确地执行一致性模型时, 独立和验证一致性协议可能
是有意义的然后通过  SMWR  公理化地抽象它。

11.3.2  测试
正式证明实现满足规范提供了很高的信心, 即实现确实是正确的。然而,
形式验证并不是万灵药。原
因有三。
首先,形式验证难以扩展。模型检查等自动技术可能无法处理复杂实现的庞大状态空间; 另
一方面,
能够处理如此大的状态空间的技术并不是完全自动化的, 对架构师来说并不容易。其次,

确地正式指定一个实现并不简单,而且可能容易出错。 第三,
即使模型是准确的,它也可能是不完整
的,
因为模型可能没有考虑到实施的某些部分。 这就是为什么彻底测试最终实施非常重要的原因。 我
们将测试分为两类:离线测试和在线测试。
在前者(也称为静态测试) 中,在部署之前测试真实或模
拟的多处理器是否存在一致性违规。对于后者, 违规会在部署后的运行时检测到。
Machine Translated by Google

266  11.  指定和验证内存一致性模型

离线测试想法
是在真实(或模拟)
机器上运行测试程序,
观察它们的行为,
并验证它们。
该方法提出了两
个问题:
如何生成测试程序?
他们的行为如何得到验证?

TSOtool  [15]是内存一致性测试的早期作品之一,它使用伪随机生成器来生成简短的测试
程序。 给定一个以前看不见的随机生成的程序, 如何验证其观察到的行为(读取返回的值)? 这是  
TSOtool  解决的关键挑战。 直觉上,这需要一个一致性检查器从观察中动态重建全局顺序并检查一
致性模型是否允许该顺序虽然从读取返回的值重建全局顺序是一个  NP  完全问题, 但  TSOtool  提
出了一个多项式算法, 以牺牲准确性来换取性能(它可能会错过一些违规行为)。  MTraceCheck  
[19]进一步改进了这个检查器。

除了使用随机生成的程序, 还可以使用⽯蕊测试(第11.2.1  节)。
使用⽯蕊试纸的好处是, 一套测试已经可用于大多数内存模型; 此外, 对于每个⽯蕊测试, 预期行为都
是已知的, 这就不需要复杂的检查器来重建全局内存顺序。 ⽯蕊工具  diy  (http://diy.inria.fr/doc/
litmus.html)是使用此方法测试真实处理器的框架。

虽然⽯蕊测试和随机测试在后硅环境中有效, 但在慢速模拟器环境中可能效果不佳。  
McVersi  [14]提出了一种基于遗传编程的方法,
该方法利用模拟器环境的白盒特性来提出可能更快
地揭示一致性违规的测试程序。

在线测试离线测
试固然有益, 但由于测试的基本局限性, 它可能无法发现一些错误。 此外, 即使离线测试没有遗漏任
何错误, 硬件瞬态错误和制造错误也可能在野外导致一致性违规。 在在线测试(也称为动态测试)
中, 将硬件支持添加到多处理器以检测执行期间的此类违规。 一种概念上直接的方法是像在  
TSOtool  中那样在硬件中实现一致性检查器。 然而,
这种方案的简单实现在内存(元数据) 和通信开
销方面将是不切实际的昂贵。 陈等。  [9]提出了一种方案来显着降低这些成本, 利用并非所有内存操
作都需要跟踪的事实(例如, 不需要跟踪对局部变量的加载和存储)。  Meixner  和  Sorin  [25]提
出了一种替代策略, 将验证任务减少为验证两个不变量的任务: (a)  检查缓存一致性协议是否正确执
行  SWMR;  (b)  检查管道是否与一致性协议正确交互。 值得注意的是, 它们表明可以在硬件中有效
地检查这两个不变量。
Machine Translated by Google

11.4.历史和延伸阅读  267

11.4  历史和延伸阅读

在  Lamport  定义  SC  的同一篇论文中, 他还提供了(它的第一个) 操作规范。 该规范是第  11.1.1  节中我们


的  SC  操作规范  1  的变体, 在语义上等同于该规范。 有趣的是, SC  的第一个操作规范采用了可线性化的存储
系统。  (Herlihy  和  Wing  [16]在十年后正式确定了线性化能力。) Afek  等人提出了第一个使用一致性导
向(即非线性化) 一致性协议的  SC  操作规范。  [3].  Shasha  和  Snir  [34]是第一个提供  SC  公理化规范的
人。

1990  年代和  2000  年代初期,
对正式验证一致性协议进行了重要研究, 不仅针对  SWMR  等不变量,
还针对内存一致性模型[32]。  Park  和  Dill  [29]展示了如何使用定理证明器抽象和验证  FLASH  多处理器
协议中使用的协议。  Qadeer  [33]表明, 针对  SC  的验证是不可判定的,
但对于大多数实际协议来说可以
变得易于处理。  Chatterjee  等人。  [8]展示了如何在模型检查器中根据弱内存模型的操作规范自动验证
管道与一致性协议的结合。

由于正式验证一致性协议的复杂性 状态空间爆炸与参数化的复杂性相结合 人们一直在努力设


计一致性协议, 以支持对无限数量的处理器进行直接参数化验证  [ 37、 38 ] .另一个有前途的方向是一致性
协议的构造正确方法。  TRANSIT  [36]和  verC3  [13]利用程序综合技术在给定部分实现的情况下自动完成
一致性协议。  Proto  Gen  [28]提出了一种自动生成完整的并发协议(具有瞬态和动作) 的方法,给定一个
只有稳定状态和瞬态的原子协议

系统。

在本章中, 我们重点介绍了验证微体系结构规范(例如, 管道和一致性协议) 是否满足体系结构一致


性规范的方法。 然而, 完整的验证需要跨越从高级语言(通过编译器、 ISA、
微架构)
到  RTL  的堆栈。  
TriCheck  [35]验证  HLL、
编译器、 ISA  和微体系结构是否满足一组⽯蕊测试的语言级一致性规范。  
RTLCheck  [23]验证  RTL  忠实地实现了一组⽯蕊测试的微架构一致性规范。

最后,
我们必须在这里强调,
我们只是触及了这个话题的表面,
是,
尽管它的历史悠久,
继续看到令人兴奋的新成果。

11.5  参考资料

[1]  Linux  内核邮件列表:
自旋解锁优化。  https://lists.gt.net/引擎?
post=105365;list=linux  260
Machine Translated by Google

268  11.  指定和验证内存一致性模型

[2]  M.  Abadi  和  L.  Lamport。
细化映射的存在。
理论计算机科学,  82(2):253–284,  1991.  DOI:  10.1109/lics.1988.5115  
263

[3]  Y.  Afek、
GM  Brown  和  M.  Merritt。
惰性缓存。  ACM  编程语言和系统事务,  15(1):182–205,  1993.  DOI:  
10.1145/151646.151651  255,  267

[4]  J.  Alglave、
L.  Maranget  和  M.  Tautschnig。
放牧猫:
弱记忆的建模、
仿真、
测试和数据挖掘。  ACM  编程语言和系统
事务,  36(2):7:1–7:74,  2014.  DOI:  10.1145/2627752  256

[5]  CJ  Banks、
M.  Elver、
R.  Hoffmann、
S.  Sarkar、
P.  Jackson  和  V.  Nagarajan。
针对弱内存模型验证惰性缓存一致性
协议。
在计算机辅助设计  (FMCAD)  中的形式化方法,
第  60‑67  页,
奥地利维也纳,
2017  年  10  月  2‑6  日。

DOI:  10.23919/fmcad.2017.8102242  264

[6]  A.  Biere、
A.  Cimatti、
EM  Clarke  和  Y.  Zhu。
没有  BDD  的符号模型检查。
在系统构建和分析的工具和算法中,
第  5  届国际会议  (TACAS)作为  Proc  的一部分举行。
欧洲软件理论与实践联合会
议  (ETAPS),
第  193‑207  页,
荷兰阿姆斯特丹,
1999  年  3  月  22‑28  日。
DOI:
10.21236/ada360973  263

[7]  JR  Burch、
EM  Clarke、
KL  McMillan  和  DL  Dill。
使用符号模型检查的时序电路验证。
在过程中。
第  27  届  ACM/IEEE  
设计自动化会议,
第  46‑51  页,
佛罗里达州奥兰多,
1990  年  6  月  24‑28  日。
DOI:
10.1109/dac.1990.114827  263

[8]  P.  Chatterjee、
H.  Sivaraj  和  G.  Gopalakrishnan。
针对弱内存模型的共享内存一致性协议验证:
通过模型检查进行
改进。
在过程中。
计算机辅助验证,
第  14  届国际会议  (CAV),
第123‑136  页,
哥本哈根,
丹麦,
2002  年  7  月  27‑31  日。
DOI:
10.1007/3‑540‑45657‑0_10  267

[9]  K.  Chen、
S.  Malik  和  P.  Patra。
使用约束图检查对内存排序进行运行时验证。
第14  届国际高性能计算机体系结构会
议  (HPCA‑14),
第415‑426  页,
犹他州盐湖城,
2008  年  2  月  16‑20  日。
DOI:  10.1109/hpca.2008.4658657  266

[10]  J.  Choi、
M.  Vijayaraghavan、
B.  Sherman、
A.  Chlipala  和  Arvind。  Kami:
一个用于高级参数化硬件规范及其模块
化验证的平台。  PACMPL,  1(ICFP):24:1–24:30,  2017.  DOI:  10.1145/3110268  264

[11]  C.  Chou、
PK  Mannava  和  S.  Park。
缓存一致性协议参数化验证的一种简单方法。
在过程中。
计算机辅助设计中的形
式化方法,
第  5  届国际会议  (FMCAD),
第382‑398  页,
德克萨斯州奥斯汀,
2004  年  11  月  15‑17  日。

DOI:
10.1007/978‑3‑540‑30494‑4_27  263
Machine Translated by Google

11.5。
参考文献  269

[12]  DL莳萝。 墨菲验证系统。 在过程中。 计算机辅助验证, 第  8  届国际会议  (CAV),


第  390‑393  页,
新泽
西州新不伦瑞克, 7  月  31  日至  8  月  3  日,
1996.  262

[13]  M.  Elver、CJ  Banks、
P.  Jackson  和  V.  Nagarajan。  Verc3:
用于并发系统显式状态综合的库。
在欧洲会议和展览  (DATE)  的设计、 自动化和测试中, 第1381‑1386  页,
德国德累斯顿,
2018  年  
3  月  19‑23  日。 DOI:  10.23919/date.2018.8342228  267

[14]  M.  Elver  和  V.  Nagarajan。  Mcversi:
用于模拟中快速内存一致性验证的测试生成框架。 在
IEEE  高性能计算机体系结构  (HPCA)  国际研讨会上, 第  618‑630  页,
西班牙巴塞罗那,
2016  年  
3  月  12‑16  日。
DOI:  10.1109/hpca.2016.7446099  261,  266

[15]  S.  Hangal、D.  Vahia、
C.  Manovit、
JJ  Lu  和  S.  Narayanan。  TSOtool:使用内存一致性模型验
证内存系统的程序。 第31  届计算机体系结构国际研讨会  (ISCA), 第  114‑123  页,
德国慕尼黑,
2004  年  6月19‑23  日。DOI: 10.1109/isca.2004.1310768  261、 266

[16]  M.  Herlihy  和  JM  Wing。
线性化: 并发对象的正确性条件。  ACM  编程语言和系统汇刊,  
12(3):463–492,  1990。
DOI:  10.1145/78969.78972  255,  267

[17]  R.  Jhala  和  KL  McMillan。
通过组合模型检查进行微体系结构验证。 在过程中。 计算机辅助验证,
第  13  届国际会议  (CAV), 第  396‑410  页,
法国巴黎,
2001  年  7  月  18‑22  日。
DOI:
10.1007/3‑540‑44585‑4_40  263

[18]  L.  兰波特。 分布式系统中的时间、 时钟和事件顺序。  ACM  通讯,  21(7):


558–565,
1978。
DOI  :
10.1145/359545.359563  262

[19]  D.  Lee  和  V.  Bertacco。  MTraceCheck:在硅后验证中验证内存一致性模型的非确定性行为。
在过程中。 第  44  届年度国际计算机体系结构研讨会  (ISCA), 第201‑213  页,
加拿大安大略省多伦
多市, 2017  年  6  月  24‑28  日。
DOI:
10.1145/3079856.3080235  266

[20]  D.  Lustig、
M.  Pellauer  和  M.  Martonosi。  PipeCheck:
指定和验证内存一致性模型的微架构
实施。 第47  届  IEEE/ACM  国际微架构研讨会  (MICRO), 第635‑646  页,
英国剑桥,
2014  年  12  月  
13‑17  日。 DOI  :10.1109/micro.2014.38  258、 264

[21]  D.  Lustig、
G.  Sethi、
M.  Martonosi  和  A.  Bhattacharjee。  COATCheck:在硬件操作系统接
口上验证内存排序。 在过程中。 第  21  届编程语言和操作系统架构支持国际会议  (ASPLOS), 第
233‑247  页, 佐治亚州亚特兰大, 2016  年  4  月  2‑6  日。
DOI:10.1145/2872362.2872399  264
Machine Translated by Google

270  11.  指定和验证内存一致性模型

[22]  YA  Manerkar、
D.  Lustig、
M.  Martonosi  和  A.  Gupta。  PipeProof:
微架构规范的自动内存一致性证明。
第51  届  
IEEE/ACM微架构国际研讨会  (MICRO),
第  788‑801  页,
日本福冈,
2018  年  10  月  20‑24  日。
DOI:
10.1109/
micro.2018.00069  265

[23]  YA  Manerkar、
D.  Lustig、
M.  Martonosi  和  M.  Pellauer。  RTLCheck:
验证  RTL  设计的内存一致性。
在过程中。
第  
50  届  IEEE/ACM  微架构国际研讨会  (MICRO),
第463‑476  页,
马萨诸塞州剑桥市,
2017  年  10  月  14‑18  日。
DOI:
10.1145/3123939.3124536  267

[24]  YA  Manerkar、
D.  Lustig、
M.  Pellauer  和  M.  Martonosi。  CCICheck:
使用  hb  图验证一致性‑一致性接口。
在过程
中。
第  48  届国际微架构研讨会  (MICRO),
第  26‑37  页,
夏威夷威基基,
2015  年  12  月  5‑9  日。
DOI  :  
10.1145/2830772.2830782  259、
264

[25]  A.  Meixner  和  DJ  Sorin。
顺序一致性的动态验证。
第32  届计算机体系结构国际研讨会  (ISCA),
第  26‑37  页,
麦迪逊,
威斯康星州,
2005  年  6  月  4‑8  日。
DOI:
10.1109/isca.2005.25  266

[26]  A.  Meixner  和  DJ  Sorin。
缓存一致性多线程计算机体系结构中内存一致性的动态验证。
在过程中。
可靠系统和网络  
(DSN)  国际会议,
第  73‑82  页,
宾夕法尼亚州费城,
2006  年  6  月  25‑28  日。

DOI:  10.1109/  dsn.2006.29262

[27]  R.  米尔纳。
程序间模拟的代数定义。
在过程中。
第二届国际人工智能联合会议,
第  481‑489  页,
英国伦敦,
1971  年  9  
月  1‑3  日。
263

[28]  N.  Oswald、
V.  Nagarajan  和  DJ  Sorin。  ProtoGen:
根据原子规范自动生成目录缓存一致性协议。
在第  45  届  ACM/
IEEE  年度国际计算机体系结构研讨会  (ISCA)  中,
第247‑260  页,
加利福尼亚州洛杉矶,
2018  年  6  月  1‑6  日。
DOI:
10.1109/isca.2018.00030  267

[29]  S.  Park  和  DL  Dill。
通过聚合分布式事务来验证  FLASH  缓存一致性协议。
在SPAA中,
第  288–296  页,
1996  年。
DOI:
10.1145/237502.237573  267

[30]  M.  Plakal、
DJ  Sorin、
A.  Condon  和  MD  Hill。  Lamport  时钟:
验证目录缓存一致性协议。
在SPAA中,
第  67–76  页,
1998。
DOI  :
10.1145/277651.277672  262

[31]  A.  Pnueli。
程序的时间逻辑。
在第  18  届计算机科学基础年会上,
第  46‑57  页,
罗德岛普罗维登斯,
1977  年  10  月  31  日
至  11  月  1  日。
DOI:10.1109/sfcs.1977.32  252
Machine Translated by Google

11.5。
参考文献  271

[32]  F.  Pong  和  M.  Dubois。
缓存一致性协议的验证技术。  ACM计算调查,  29(1):
82–126,
1997。
DOI  :
10.1145/248621.248624  267

[33]  S.卡迪尔。
通过模型检查验证共享内存多处理器上的顺序一致性。  IEEE  Transactions  on  Parallel  Distributions  
on  Systems,  14(8):730–741,  2003。
DOI:
10.1109/tpds.2003.1225053  267

[34]  DE  Shasha  和  M.  Snir。
高效且正确地执行共享内存的并行程序。  ACM  编程语言和系统汇刊,  10(2):282–312,  
1988。
DOI:10.1145/42190.42277  267

[35]  C.  Trippel、
YA  Manerkar、
D.  Lustig、
M.  Pellauer  和  M.  Martonosi。  TriCheck:
软件、
硬件和  ISA  三部分的内存
模型验证。
在过程中。
第  22  届编程语言和操作系统架构支持国际会议  (ASPLOS),
第119‑133  页,
中国西安,
2017  
年  4  月  8‑12  日。
DOI:  10.1145/3037697.3037719  267

[36]  A.  Udupa、
A.  Raghavan、
JV  Deshmukh、
S.  Mador‑Haim、
MMK  Martin  和  R.  Alur。
TRANSIT:
使用  concolic  片段指定协议。  ACM  SIGPLAN编程语言设计与实现会议  (PLDI),
第  287‑296  页,
华盛
顿州西雅图,
2013  年  6  月  16‑19  日。
DOI:
10.1145/2491956.2462174  267

[37]  M.  Zhang、
JD  Bingham、
J.  Erickson  和  DJ  Sorin。  PVCoherence:
设计用于可扩展验证的平面一致性协议。

20  届  IEEE  高性能计算机体系结构  (HPCA)  国际研讨会,
第  392‑403  页,
佛罗里达州奥兰多,
2014  年  2  月  15‑19  
日。
DOI:
10.1109/mm.2015.48  267

[38]  M.  Zhang、
AR  Lebeck  和  DJ  Sorin。
分形一致性:
可扩展可验证的缓存一致性。
在第  43  届年度  IEEE/ACM  微架构
国际研讨会  (MI  CRO)  中,
第  471‑482  页,
佐治亚州亚特兰大,
2010  年  12  月  4‑8  日。
DOI:
10.1109/micro.2010.11  
267
Machine Translated by Google
Machine Translated by Google

273

作者传记

VIJAY  NAGARAJAN  Vijay  
Nagarajan  (http://homepages.inf.ed.ac.uk/vnagaraj/)是爱丁堡大学信息学院
的一名读者。 他获得了博士学位。 加州大学河滨分校计算机科学专业。 他的研究兴趣
涵盖计算机体系结构、 编译器和计算机系统, 重点是内存一致性模型和缓存一致性协
议。 他是英特尔早期职业教师荣誉奖、 PACT  最佳论文奖和  IEEE  Top  Picks  荣誉奖
的获得者。 他曾(或目前正在) 服务于多个项目委员会, 包括  ISCA、MICRO  和  HPCA。
他曾担任  LCTES  2017  大会主席, 目前担任IEEE  Computer  Architecture  Letters  
(IEEE  CAL)  的副主编。

DANIEL  J.  SORIN  
Daniel  J.  Sorin是杜克大学电气与计算机工程和计算机科学教授。 他的研究兴趣是
计算机体系结构, 包括可靠体系结构、 验证感知处理器设计和内存系统设计。 他获得
了博士学位。 他在威斯康星大学获得电气和计算机工程硕士学位, 并在杜克大学获得
电气工程学士学位。 他是  NSF  职业奖的获得者, 并且是英国皇家工程院的杰出访问
学者。 他是  IEEE  Computer  Architecture  Letters  的主编,
也是  Realtime  
Robotics,  Inc.  的创始人兼首席架构师。 他是之前综合讲座Fault  Tolerant  
Computer  Architecture  (2009)  的作者。

马克·希尔
马克·D·希尔(http://www.cs.wisc.edu/~markhill)是威斯康星大学麦迪逊分校的  John  P.  
Morgridge  教授和  Gene  M.  Amdahl  计算机科学教授, 在那里他还获得了电气和计算机工程专业的
礼遇任命。 他的研究兴趣和成就在于并行计算机系统设计(例如, 无数据争用内存一致性)、 内存系统
设计(3C  模型: 强制性、 容量和冲突缺失) 和计算机仿真(GEMS  和  gem5)。  Hill  的工作与  160  多
位合著者高度合作, 尤其是他的长期同事  David  A.  Wood。他获得了  2019  年  Eckert‑Mauchly  奖和  
2009  年  ACM  SIGARCH  Alan  Berenbaum  杰出奖
Machine Translated by Google

274  作者传记

服务奖。  Hill  是  IEEE  和  ACM  的会员。
他于  2018‑2020  年担任计算机社区联盟主席,
并于  
2014‑2017  年担任威斯康星州计算机科学系主任。 希尔拥有博士学位。 加州大学伯克利分校计算
机科学专业。

大卫·伍德
David  A.  Wood是威斯康辛大学麦迪逊分校计算机科学名誉教授, 曾担任计算机科学  Sheldon  
B.  Lubar  主席、 计算机科学  Amar  和  Balinder  Sohi  教授, 并在  Elec  trical  担任礼貌任命和计
算机工程。 伍德博士拥有博士学位。 加州大学伯克利分校计算机科学专业  (1990)。  Wood  博士
是  ACM  研究员  (2006)、 IEEE  研究员  (2004)、
UW  Vilas  Associate  (2011)、UW  Romnes  研究
员  (1999)  和  NSF  PYI  (1991)。  Wood  博士曾担任  ACM  SIGARCH  主席、  ACM  TOMACS  区
域编辑(计算机系统)、  ACM  TACO副主编、  ASPLOS‑X  (2002)  计划委员会主席, 并在多个
计划委员会任职。  Wood  博士发表了  100  多篇技术论文, 并且是  19  项美国专利的发明人。  
Wood  博士与他的长期合作者  Mark  D.  Hill  共同领导了威斯康星风洞和威斯康星多方面项目。

You might also like