Home 《内存一致性与缓存一致性》笔记(八):目录一致性协议
Post
Cancel

《内存一致性与缓存一致性》笔记(八):目录一致性协议

我们在上一节中注意到,总线嗅探协议的扩展性较差。我们接下来介绍目录协议,不需要可以保证全局顺序的广播网络。和之前一样,我们先从MSI开始介绍简单的目录协议,然后增加功能并进行优化,进一步讨论更复杂的协议,介绍如何表示目录状态以及如何实现目录协议。

目录协议的关键是建立一个目录,维护每个缓存块的一致性状态,跟踪哪些缓存拥有缓存块以及处于什么状态。需要发出一致性请求的缓存控制器直接发送给目录(单播),目录确定接下来要采取的行动,例如目录状态可能表示所请求的块在C2的缓存,因此请求应该被转发给C2(比如可以使用一个新的Fwd-GetS请求),C2的缓存控制器收到这个转发的请求时,向请求者发送响应。这一过程通常涉及两个步骤(一个单播请求和一个单播响应)或三个步骤(一个单播请求,K个转发请求和K个响应),有的协议甚至涉及第四步,相比之下总线嗅探协议只会有两个步骤(即广播请求和单播响应)。

目录协议同样需要确定事务顺序,在大多数目录协议中,一致性事务是在目录中排序的。如果有两个请求先后到达,第二个请求有可能在第一个请求之后立即被处理,或者停顿,或者被否定确认(即NACK),最后一种情况需要妥善处理活锁的问题。然而,相比总线嗅探协议,目录协议在目录中对事务进行排序,没有一个全局的顺序。我们可以发现目录协议实现了更好的可扩展性,但代价是一个层次的间接性,会增加一些事务的延迟。

最简单的目录一致性协议系统

和之前一样,我们从最简单的模型开始讨论。下图展示了我们现在讨论的系统模型,注意和之前的模型不同,这里互联网络的拓扑结构是不确定的,可以是任何形式的拓扑结构,不过需要执行点对点的排序,也就是说如果A对B发送了两条消息,这两条消息是需要顺序的,这可以降低协议设计的复杂性。同时,我们在LLC的旁边增加了一个目录,并把内存控制器改名叫目录控制器。我们现在假设一个最简单的模型,对内存中的每个块都有相应的目录条目,之后会讨论更实用的目录组织方式。

协议规范

我们仍然从MSI协议开始。目录状态包括一致性状态、缓存块所有者(M状态)以及共享者(S状态),一个简单的目录条目如下图所示。

8-2 N节点系统中的目录条目

下图中展示了缓存控制器发出一致性请求的过程,我们仍然使用以缓存为中心的符号来表示目录的状态。注意缓存控制器不能自己驱逐一个共享块,而是需要发出一个PutS请求。

8-3 目录协议中的状态变化

避免死锁

在这种协议中,一个消息的接收可以导致一致性控制器发送另一个消息,且消息需要占用一些资源,我们需要注意避免可能产生的死锁。下图说明了一种死锁产生的情况以及可能的解决方案,包括为每一类消息使用单独的网络,可以是物理上分离的或者逻辑上分离的。

8-4 目录协议中的死锁

本节中的目录协议使用三个网络来避免死锁,分别为请求消息(GetS、GetM、PutM)、转发请求消息(Fwd-GetS、Fwd-GetM、Invalidation)和响应消息(Data、Inv-Ack)的网络。

协议细节

下表介绍了详细的协议细节,包括过渡状态。

8-5 缓存控制器视角下的协议细节

8-6 内存控制器视角下的协议细节

具体的状态变化的细节可以参考原书。这个协议相对来说比较简单,牺牲了一些性能。最重要的简化是协议在某些情况下会停顿,可以通过添加更多的过渡状态来提升性能,其次第二个简化是目录发送数据(和AckCount)来响应缓存将一个缓存块的状态从S升级到M的请求,但其实缓存中已经有数据了,不再需要发送数据,我们可以定义一种新的消息类型。

添加Exclusive状态

处于E状态的缓存是该缓存块的所有者,负责对请求做出响应,不过需要注意E状态缓存块的驱逐需要额外的PutE请求,把所有者转交给目录。当然E状态缓存块也可以不是所有者,但是会增加协议的复杂性。

协议规范

下图展示了MESI协议中的状态变化,粗线突出了与MSI协议的区别。

8-7 MESI协议中的状态变化

协议细节

下表介绍了详细的协议细节,包括过渡状态。

8-8 缓存控制器视角下的协议细节

8-9 内存控制器视角下的协议细节

表示目录状态

之前我们总是假设存在一个完整的目录,维护了每个缓存块的状态,但是这违背了可扩展性,需要占用大量的存储空间,因而我们需要有可扩展的方案来维护目录状态。

8-10 在C个节点的系统中维护目录状态

粗略(Coarse)的目录

减少目录占用空间的一个办法是保守地维护一个粗略的共享者列表,比如用一位来表示K个缓存,表明这K个缓存中有一个或多个缓存可能有状态为S的缓存块,一个GetM请求会往这K个缓存都发送Invalidation消息。好处在于减少了一个目录条目的空间,代价则是可能会增加网络带宽,发送一些无效的消息。

有限指针式(Limited Pointer)目录

研究表明许多缓存块的共享者通常为0或者只有一个共享者。在这种设计中,我们通过i(i小于C)个条目来进行优化,并利用一些额外的机制来处理i+1个或更多共享者的情况。

目录组织形式

逻辑上来说,目录会包含每个缓存块的条目。许多传统的基于目录的系统,目录控制器与内存控制器集成在一起,通过增加内存来放置目录,直接实现了这种逻辑抽象。然而在现代的多核处理器和LLC中,原来设计会导致延迟和功耗很大,且当大多数缓存块都没有被缓存时,目录状态占用的空间也很大。

我们可以采用缓存的思想,实现一个目录缓存。目录缓存对一致性协议的功能没有影响,只是为了降低平均目录访问延迟。关键的设计问题是处理目录缓存缺失的情况下,也就是当一致性请求到达目录但目录条目不在缓存中的情况。下表总结了一些设计方案。

8-11 目录缓存的设计

DRAM支持的目录缓存

最简单的做法是把完整的目录保存在DRAM中,并使用单独的目录缓存结构来减少延迟,如果缓存未命中则访问DRAM中的目录。这种设计有一些缺点,首先需要大量DRAM来保存目录(包括没有被缓存的块),其次目录缓存和LLC解耦,可能LLC命中但是目录缓存未命中,最后目录缓存替换必须将目录条目写回DRAM,延迟和功率都高。

包容性(Inclusive)目录缓存

我们实际上只需要缓存LLC上缓存块的目录状态即可,如果一个目录缓存包括了片上缓存所有块的超级,我们将其称为包容性目录缓存,因而也不需要在DRAM中存储完整的目录了。接下来讨论两种包容性目录缓存设计。

嵌入在包容性LLC中的包容性目录缓存

最简单的目录缓存设计依赖于LLC,与上层缓存保持包容,也就是说如果一个块在上层缓存中,那么也必须出现在下层缓存中。在这种设计中,只需要在每个缓存块中额外增加几位表示目录状态即可。如果一个一致性请求发送到LLC上的目录控制器,而LLC未命中,那么目录控制器就可以确定该缓存块没有被缓存,在所有L1缓存中都处于I状态。

不过,这种设计依然有几个重要的缺点。首先,在替换LLC中的缓存块时,通常需要发送特殊的召回请求,使L1缓存中的缓存块失效;更重要的是,LLC的包容性使其需要存储上层缓存中数据的冗余副本,在多核处理器中非常占用空间。

独立式包容性目录缓存

目录缓存也可以不依赖于LLC,而是一个独立的结构,只是在逻辑上于目录控制器关联,而非嵌入到LLC内。为了使目录缓存保持包容性,必须包含所有L1缓存中的目录条目。这种设计更加灵活,不需要包含LLC的内容,但相对来说实现比较复杂。最明显的是,这种设计需要一个组相联度较高的目录缓存,例如考虑一个有C个内核的处理器,每个内核都有一个K路组相联的L1缓存,那么目录缓存必须是C*K路组相联的。下图展示了K=2时的情况。

8-12 独立式包容性目录缓存结构

性能和可扩展性优化

分布式目录

8-13 分布式目录多处理器系统模型

上图说明了有N个节点的多处理器系统模型,内存地址对节点的分配通常是静态的。这种设计可以提供更高的带宽,且对一致性协议没有影响。在现代大型LLC和目录缓存的处理器中,分配目录的方法在逻辑上仍然采用这种方式,分配LLC和目录缓存。

不停顿的目录协议

我们之前在讨论一致性协议时,有很多情况都会导致停顿,但我们可以修改一些协议细节,使一致性控制器跟踪正在处理的额外消息,添加一些额外状态来减少停顿。原文中给出了一种之前MSI协议细节的变体,限于篇幅这里就不贴出了。

没有点对点排序的互联网络

我们之前假设了互联网络中点到点的消息是顺序的,如果没有这个假设,可能会出现竞争,例如下面的例子中展示了如果没有点对点排序可能会出现死锁。

8-14 没有点对点排序可能会导致死锁

This post is licensed under CC BY 4.0 by the author.

《内存一致性与缓存一致性》笔记(七):总线嗅探一致性协议

《内存一致性与缓存一致性》笔记(九):异构系统的内存一致性与缓存一致性