chapter7:snooping coherence 协议
简介
窥探(Snooping)缓存一致性协议是最早被广泛使用的协议并被沿用至今。它有较短的一致性传输延时以及相对于目录(directory)协议更简单的设计。
窥探协议通过要求对一个缓存行的所有要求按顺序到达,实现了所有分布式缓存控制器都能正确更新共同表示缓存行 state的有限状态机。
在表7.1中,针对缓存行 A,Core1、Core2、LLC/Memory以相同的顺序观察C1发出的GetM、C2发出的GetM,最终得到了正确的运行结果。但在表7.2中,Core C2先观察到了C2发出的GetM,再观察到C1发出的GetM。这样会导致在Time1时Core C2完成写入操作并转变为M状态,在Time2时Core2看到了Core1的GetM,于是自发把缓存行 invalidate,此时系统中没有Core认为自己是M状态,但除了Core2以外的其它Core都认为Core2是M状态,于是如果总线上出现了GetS请求,Core2应该对其发送DataResp,但I状态的Core2不会发送,因此可能导致请求者持续Stall等待数据,导致系统死锁。
基本snooping协议
这里讨论一个最简单的、没有优化的协议,并描述在两个不同的系统模型上的实现,前者说明了实现的基本方法,后者更复杂一些,表明即使是相对简单的性能改进的方法也会影响协议实现的复杂性。
- M(Modified):该缓存行是有效的(valid),独有的(exclulsive),有所有权的(owned);有可能是脏的(dirty)。可以对其进行读写操作。
- S(Shared):该缓存行是有效的(valid),干净的(not dirty),无所有权的(not owned)。可以对其进行读操作。
- I(Invalid):该缓存行是无效的(invalid)。不能对其进行读写操作。
原子请求与原子事务
更加详细地说,原子请求表示一致性请求是在请求从缓存控制器发出的同一周期在总线上排序的,这消除了缓存行从发出请求到请求在总线上出现之间的状态变化——不考虑在这之间由于别的一致性消息而更改状态的情况,这是一种简单情况的考虑。
非原子请求、原子事务
在实际硬件实现过程中,许多优化措施(如在缓存控制器与bus之间插入queue)会导致非原子请求的情况出现,这种情况也是在教材中作为基准窥探系统模型来分析的。当考虑非原子的情况时,根据上述分析可以得知主要增加了“缓存控制器发出请求”到“请求出现在总线上”这段时期内缓存控制器接收到别的一致性消息的可能.
加入独占态(MESI)
观察一个常用的数据处理场景:一个核先读了一个缓存行,再写了这个缓存行。如果使用MSI协议实现,那么默认该缓存行在LLC中是I状态,传输流程是:1)Core 发送GetS来获取读权限;2)Core 发送GetM来获取写权限。
MESI协议则加速了这种行为。当GetS发生时没有其它cache拥有目标缓存行时,缓存控制器允许该缓存行以E状态被获取。当后续对该缓存行有store请求时,该块可以“无声地(silently)”转换为M状态,即从E转换为M状态时不需要发送任何一致性请求。
进入E状态
-
通过或门实现:在总线上增加nbit对应n个缓存控制器连接至一个或门,当GetS出现在总线上时,所有share这个缓存行的共享者 将总线上属于自己的这一bit置位,运算后的结果被请求者(Req)观察,若结果为1则缓存行 state变为S,否则为E。
-
让LLC在原本的“IorS”状态下区分I状态与S状态,若LLC为I状态则变为E状态,若LLC为S状态则变为S状态。要实现这种情况同样具有两个方法:
-
- 一个方法是让每个缓存控制器显示地发送PutS给LLC,同时目录需要记录共享者的个数以便在最后一个共享者的PutS收到后转变为I状态。
- 另一个简单但不完全的实现方法是允许LLC保守地记录共享者,即LLC不记录共享者的个数,一旦收到第一个GetS就转变为S状态,只有收到PutM后才会转变为I状态。这样做放弃了很多使用E状态的机会,因为S状态的LLC代表着记录0个或多个共享者,而我们放弃了记录0个共享者时使用E状态的机会。
添加所属态(MOSI)
提出动机
在MSI或MESI协议中,如果缓存行为M或者E状态时遇到了GetS请求,则其在状态变为S的同时,向请求者和LLC/memory同时发送数据,因为此时该缓存行放弃了所有权,而将所有权归还给了LLC/memory。LLC/memory变成了owner,有最新的数据副本,并负责响应后续请求。
在这种情况下,引入O状态,即在M或E状态收到GetS请求后,该缓存行转变为O状态,依然负责响应后续的请求。这样做有两个好处:
- 免去了向LLC/memory传输、写入(如果是dirty)数据的过程,减少了transactions和对传输带宽的需求。
- 当后续的请求到来时,不需要从LLC/memory搬运数据,大量减少数据传输延时。