缓存一致性协议如何适应更大规模的系统。广播和侦听协议更早地涉及了可扩展性问题,因为流量和侦听频率时随着处理器个数的增加至少呈线性增加趋势,可用的互连网络带宽会很快被广播流量占满。本章讨论的基于目录式缓存一致性协议来实现可扩展性。主要问题在于如何处理协议竞争,以及瞬时状态的使用等。以及,讨论当前多核设计问题,例如如何处理不精确的目录信息、讨论一致性需要被以单个粒度来跟踪还是以多个粒度来跟踪、如何设计一致性可允许多核系统系统执行分区,以及线程迁移代价如何降低。
10.1 目录式一致性协议
可伸缩性最好的组织方式是使用点对点互连方式,以及目录方式,而不是广播和侦听方式。在目录方式中,有关那个缓存保存了那个数据块的副本这类信息被保存在一个称为目录的数据结构中。利用这种方式,定位一个被缓存的数据块不再依赖于将一个请求广播到所有处理器,而是检索目录,找出该数据块相关联的缓存,然后只向这些缓存发送请求。如果一个数据块在所有处理器中保保存了副本,那么这个请求必须发送到所有处理器,这会产生与广播/侦听方式相类似的流量。然而,如果一个块只被少量处理器缓存了,那么目录方式的流量相比广播/侦听方式的流量将有根本的变化。
经过调优的程序很少出现频繁读写共享数据。因此,大多数的数据共享只发生在只读数据,读写数据块很少被共享。对于这种特点的数据来说,写操作一般只会影响到很少的数据块(如将被其置为无效),相比于广播/侦听方式,目录方式可以节约大量的网络流量。有一种情况或许会造成目录式协议与广播/侦听协议相比较时优势不明显,即所有处理器频繁读写某一个变量,造成高度竞争同步。不过这种情况也暗示着应用中有大量的对数据置无效的操作,这些操作造成性能和可扩展性的限制,而总线也无法解决此类问题。
10.2 目录式一致性协议概览
图中上半部分显式读请求如何处理,下半部分写请求如何被处理:
假设数据块B被保存在P3的cache,状态是M。假设P0要读取B,引发了一个缓存缺失。这个缺失触发了一个对目录的查询(第一步)。由于P3中被缓存的这个副本是系统中唯一有效的副本,B必须从P3中得到。目录包含了必要的信息它记录了B是以M状态保存在P3中的。目录转发请求到P3的缓存(第二步)。最后,P3缓存响应请求,把数据块传送给请求者(第三步),可以直接发,也可以通过目录转发。此时与广播一致性协议相比,目录方式节约了大量的网络流量。一个广播协议会使P0将它的读缺失的请求发送到所有其他处理器/缓存中,因为它不知道哪个缓存保存有该数据的副本。
假设C被保存在P0、P1和P2的缓存中,状态是S。首先,请求者P0向目录发出一个请求,要求更新C的状态(第一步)。请求者可能有也可能没有这个数据块的副本(图中是有副本)。目录查找它的信息后发现,除了请求者,只有P1和P2保存C的副本。因此,它发生置无效操作到P1和P2(第二步)。那么数据的共享者通过发送一个置无效确认消息来做出响应,表示它已经接收到了置无效消息,并将对应数据块置为无效了(第三步)。与广播一致性相比,目录协议减少了大量网络流量,这些广播一致性协议可能会向所有其他处理器发送置无效消息。
目录格式和位置
需要强调当读/写请求到达目录后,目录必须知道:
1. 哪个缓存保存这个数据块的副本;
2. 这个数据块在每个缓存是以什么状态被保存的。
为了满足第一个要求,目录可以直接把缓存中的数据块的状态完全复制过来。例如缓存使用MESI协议时,理想情况下目录也保存一个MESI状态,可以完美地反映数据块在缓存中的状态。然而,目录状态不可能永远反映数据块的缓存状态。例如在MESI协议中,修改一个E状态缓存块时,该状态会从E改为M,而不会发出任何消息,则目录不会看到数据块从状态E变为M。因此,在运行MESI协议时,目录无法区分缓存的一个块的状态究竟是E还是M。因此,为了迎合MESI缓存协议,目录只需要保存三个状态EM、S、I。
为了知道哪个缓存保存了数据块副本,目录的格式有多种可以选择的设计方式。针对每一个特定系统选择目录格式时,需要重点考虑的问题是存储代价已经目录存储空间是否会不足而导致信息溢出。
一种格式是为每个局部缓存保留一个信息位,用来指示该数据块是否存在于这个缓存中。这种格式被称为全位向量目录格式full bit vector。假设块大小为64字节,当系统中有64个缓存,存储代价比例为64/(64*8)。显然全位向量的方法在大型系统中代价过于巨大。
降低目录存储代价的一个简单方法是使用一位来表示一组缓存而不是一个缓存。如果一个缓存组内任意一个缓存保存有某个数据块的副本,那么这个数据块中对应缓存组的值应该被置为1。反之,置为0。这个结构被称为“粗粒度向量”corse bit vector目录格式。由于信息是保存在缓存组里,当有置无效操作时,这个消息被发送给组内的每个缓存,即使其中只有一个缓存具有该块的副本。因此,这种方式相比全位向量方式数据流量增加了,然而目录的存储空间需求下降了。
另一个降低存储开销的方式是不要在内存中为数据块保存全部的目录信息。采用这种方法的一种目录较“受限的指针limited pointer”格式。该格式保存一定数量的指向共享该数据块的缓存的指针。降低存储开销的数量依赖于每个数据块最多保存多少指针。然而,一个特定的数据块实际上很少会出现在多个缓存里,所以大多数情况下4~8个指针就足够了。当共享该数据的缓存数大于指针的上限时,目录达到了它可以保存的ID数目限制。应对策略有如下几种:
1. 不要允许这种情况发生;
2. 保留一个空指针的池,指针可以被分配给遇到类似大范围数据共享的缓存数据块。
基于内存中大部分数据块根本没有被缓存,压缩目录格式被启发出来。在稀疏目录格式中,当产生一个对数据块的请求时,该请求先在目录缓存中被检索。如果检索到了,那么就可以获得目录信息,反之则这个块一定还没有被缓存过。然而,该格式存在溢出风险,原因是一个干净的块可能会被缓存“无声无息”地替换,替换时并不通知目录缓存。而目录缓存依旧保存着这个数据块的目录条目,使得有需要的缓存块无法被添加进来。
在实际系统中,目录格式并不是唯一的,可以同时采用多种格式。例如,受限的指针格式与粗粒度格式结合,让指针指向一组处理器节点而不是一个节点。最后,面对不同的实际情况,系统可以从一种格式切换到另一种格式。在SGI Origin 2000系统中,当数据块只被保存在一个处理器节点时,系统使用了一个受限指针模式的目录格式;当数据块保存到多余一个处理器节点时,使用的是全位向量格式;而在系统扩展到超大规模时,使用了粗粒度位向量格式。
目录设计中的另一个问题是目录保存在什么位置。这个问题包含多个方面。其中一个方面是目录只存在一个位置(集中式部署)还是被分成多个部分保存在不同的地方,每个部分与一部分地址相关联(分布式部署)。集中式目录设计比较简单。然而,任何集中式设计在可扩展性都会在某些情况下成为系统瓶颈:所有缓存缺失请求都会被发送到一个相同的位置,所有的置无效请求也会从同一个位置被发送出去。因此,目录的可扩展能力需要以分布式方法来实现。然而,如何设计分布式目录目前仍然是一个开放性问题。
设计分布式目录结构需要考虑:
1. 能够有效分流流量的最少分块数量。这依赖于网络的特征,如网络拓扑、带宽和时延。
2. 当一个数据块没有被缓存时目录从下一级存储器取得数据的大概时间,这决定了缓存缺失时延。
图片中间显示的目录一致性协议的系统与分布式共享存储系统有一点区别,该系统中的目录协议关联的存储器结构是缓存而不是主存。缓存可能缺失,但主存不会。如果数据块发生缺失,请求会被发到一致性目录,如果目录发现这个数据块不存在任何一个L2缓存,那么它必须从芯片外部的存储器中调取数据。因此,为了快速获取数据,将存储控制器和目录部署到一起,并以相同的交叉访问的方式来寻址目录式很有意义的。一个潜在的问题是可能没有足够数量的内存控制器来使流量在整个芯片中散步得更加均匀。因为每个内存控制器都需要固定位数的输出引脚,而处理器的引脚数一般是受限的。
图片底部展示多核芯片,L3的标签阵列和目录合并的好处是一次查询可以确定块的目录信息时,直到L3中是否存在这个数据块。这样可以降低L2缺失的延迟,已经降低L3缓存命中延迟。缺点在于目录只能保存L3中保存的块。导致L2中的块都必须存在L3中。
另一个问题是目录的信息在物理上是如何保存的。目录可能会以分离式结构实现,或者嵌入到现有的与它一起存储的结构里。分离式结构的目录比嵌入式结构的目录要求的工程代价更大,但是不会影响现有存储器结构的容量。目录也可以作为主存的一部分而保存。在启动时,硬件将一部分内存分离出来,从而使这些内存可以被一致性控制器管理,并且是操作系统可见的。目录嵌入的优点在于实现简单,而且不需要修改内存。缺点是对目录的访问会与对内存的访问产生竞争,带来显著的数据访问延迟和目录访问时延。另一种方式是将目录信息保存在与处理器相同的芯片里。优势在于本地处理器可以快速确定该数据块是否在本地缓存还是远端缓存,而不需要花时间来访问片外目录。缺点是目录会占用芯片面积,而留给其他功能模块的芯片资源会减少。
考虑一个非LLC的目录式一致性协议,以及当目录嵌入L3缓存的情况。一个好处是不需要分离的或者专用的目录结构。另一个好处是灵活性:如果缓存一致性没有被激活,那么就不会引入开销。当然将目录嵌入L3也有很多缺点。例如,目录访问和L3访问产生竞争、降低L3可用容量。
10.3 目录缓存一致性协议基础
假设在分布式共享存储的系统中实现缓存一致性。对实现的讨论主要分为2步:
1. 描述一恶搞工作协议,而不讨论一些可能得设计选择以及这些选择会带来的影响。
2. 保证正确性和性能的更多细节。
假设缓存的节点采用MESI状态,目录中每个内存数据块保存如下信息:
1. EM:该数据块以EM缓存,而且仅仅存在一个缓存里。
2. S:数据块以未被修改的状态被缓存,可能被保存在多个缓存里。
3. U:数据块没有被缓存,或者被缓存了但是状态已被置为无效。
目录无法区分某个数据块时被“干净”地独占还是“脏”的。因此,目录将这两种情况合并在一起。
该目录协议还支持一下请求类型:
1. Read:某个处理器发出的读请求;
2. ReadX:某个不具有该数据副本的处理器发出独占读/写请求;
3. Upgr:有某个已经具有该数据块的处理器发出的更新数据块的状态请求,从S改为M。
4. ReplyD:从根节点到请求节点的回复,包含一个内存数据块的值。
5. Reply:从根节点到请求节点的恢复,不包含内存数据块的值;
6. Inv:由根节点发给其他缓存的置无效请求。
7. Int:由根节点发给其他缓存的干预请求(将状态回退到S)的请求;
8. Flush:缓存块拥有者将数据块发出;
9. InvAck:确认接收到置无效请求的消息;
10. Ack:确认接收到置无效请求的消息;
前三个类型的消息(Read、ReadX和Upgr)是由请求者发出,被发送到数据块的根节点,根节点也就是数据块的目录信息所处的节点。Read是从一个未被缓存的数据块中读取数据的请求(读缺失),而ReadX和Upgr时请求写入一个数据块中,ReadX用于数据块不再缓存中,而Upgr用于数据块位于缓存中。此后两个消息(ReplyD和Reply)是目录向请求节点发出的响应。这种响应可能会包含数据。之后的两个类型的消息(Inv和Int)是由数据块的根节点发给共享者的消息(将其置为无效),或者发送给数据块拥有者的消息(将其状态回到共享)。最后三类消息(Flush、InvAck和Ack)是对Inv和Int消息的响应,用于写回/清空一个脏缓存数据块、接收到Inv消息及其他类型消息的确认。
注意有些消息会被合并成一个消息,如一个数据块的拥有者清空了这个块并确认一个置无效的请求,将合并消息称为Flush+InvAck。
目录采用的对应于一致性协议的有限自动机如图示,斜线/的左边是目录收到一个处理器的请求,圆圈中的消息是消息的发送者(请求节点)。斜线的右边对应着目录发出的消息,这些消息是对来自处理器请求的回应。目录中处理共享位向量的一致性动作没有在图中展示。
首先考虑目录状态是U,如果有一个独占读请求,目录从本地内存获取数据块,并发送一个数据响应消息给请求者,并把状态转换到EM(目录还会改变共享位向量中请求数据节点中对应的位置,不过图中没有展示)。如果是一个读请求,目录会从本地内存中取得数据块,并发送给请求者一个数据回复。请求者会是系统总第一个共享该数据块的节点,目录的状态也会随之转换为EM。
处于共享状态目录时,当存在读取请求时,该目录知道它在本地内存中具有有效块。因此,它将数据回复给请求者并相应地更新共享向量。目录状态保持共享。如果请求是独占读取的,则目录通过将块的数据值发送给请求者进行响应,并且向所有当前共享者发送无效化消息。发送给请求者的数据回复消息还包括请求者期望接收的无效确认的消息。目录状态转换为EM。但是,如果它是升级已请求,则该目录将无效化消息发送给共享者,并向请求者发送没有数据的回复。
最后,当目录状态是EM时,如果收到读取请求,则将状态改为“共享”,并向该块的所有者发送干预请求。干预请求还包含请求者的ID,以便拥有者知道往哪里发送该块。如果收到读取独占请求,则新处理器想要写入该块,因此该目录需要向当前所有者发送无效化消息。无效请求还包括请求者的ID,以便拥有者知道要发生块的位置。请注意,该块可能是拥有者的独占或修改过的旧目录,但该目录不知道是那种情况。无论那种情况,在接受干预或无效化时,拥有者必须做出适当的回应。
假设三节点多处理器,每个节点具有一个处理器、cache和目录,目录内容包含所有本地内存块的信息。假设目录格式由目录状态和全位共享向量组成。
比较基本的目录一致性协议和广播一致性协议:
1. 目录协议比较复杂,具有比基于总线的多处理器更多的消息类型,须维护根节点上的目录以及当来自不同请求的多个消息重叠时可能的协议竞争。
2. 在基于总线的多处理器中,所有事务最多只需要两跳(请求和总线上的后续应答),而目录式一致性有时需要3跳事务。
3. 基于总线的多处理器可以使用缓存到缓存的传输不予与基于目录的一致性一起应用。由于所有读取或写入失败都需要回Home,因此提供数据最快的方式是Home将其存储在本地存储器中。在这种情况下,不能使用缓存到缓存的传输。
10.4 实现正确性和性能
此前,我们假设:
1. 目录状态及其共享向量反映了块的高速缓存数据状态。
2. 在原子级上处理由请求引起的消息(不相互重叠)。
在实际系统中,这两个假设不一定适用,这会产生需要被适当处理的各种协议竞争。其中一些可以仅通过目录相对简单地处理。但在其他情况下,单靠目录无法正确处理,还必须修改每个节点上高速缓存一致性控制器的行为以帮助目录。
10.4.1 由目录状态不同步引起的竞争处理
目录状态和缓存状态可能不同步的原因在于它们没有在锁定步骤中更新,并且缓存中的某些事件从不会被目录状态看到。例如,块可以从缓存中替换,并且相应的节点不再是共享者。但是,该目录不会看到此事件并认为该节点是该块的共享者。由于目录具有不一致的缓存状态视图,可能会出现异常情况。
目录使用的一致性协议对应的状态机如图示,斜杠左侧是由从处理器接到请求,消息的发送者显式咋括号中。斜杠右侧是目录发送的消息,作为对从处理器收到的请求的响应。消息的目的地显示在括号中。
讨论由于目录具有缓存状态的不同步视图而发生的新情况。当状态是EM时,对于cache状态的不同步视图,目录可能会收到来自节点A的读取或独占读取请求。这是因为节点A在干净块被从其缓存中“悄悄”替换后,遭受读取或写入缺失(如果它是脏的,请求将会再请求者停下来,而目录会在接收到读取或者独占读取请求之前从节点A接受到一个Flush消息)。由于该目录已经将A注释为块的拥有者,它可以通过回答所请求的数据块来做出反应。
请注意,在修改状态下缓存的块可能在目录不知道的情况下被替换。当该块正被写回目录时,该节点可能遭遇同一块的读取缺失,并可能向该目录发送读取请求。在这种情况下,目录回复的数据是不正确的。但是,当目录没有即时接受回写时会出现这种情况。到目前为止,我们假设缓存可能与目录不同步,但所有事务都是瞬时发生的。
总的来说,为了完成在目录中处理缓存状态的不同步视图,可以通过对目录处的一致性协议和每个节点的缓存一致性控制器进行相对简单的修改。
10.4.2 由对请求非实时处理引起的竞争处理。
当与请求相对应的消息不回立即发送时,来自两个不同请求的消息可能会重叠。存在某两个处理器请求读写的块地址一致的情况。如果处理不当,重叠的请求处理可能会产生不正确的结果。
无效化消息比早先发送的数据回复更早到达A。这是网络中的一种可能性,它允许一对节点之间的消息使用不同的路径传播。因此,收到消息的顺序可能与它们的顺序不同。此时A应该如何回应它收到的无效化消息?注意目录和A看到的顺序相反,并且A不知道目录所看到的顺序,因此它不知道如何响应无效化消息。
如果没有重叠请求处理,当节点A收到一个无效化消息以无效化在其缓存中找不到的块时,它就知道无效化指向已经被逐出的块,这种情况下正确反应时确认无效化消息已被收到,因此它不再有该块,就好像使块刚刚无效。但是在重叠请求处理的情况下,接收非缓存无效化消息可能由于提前无效竞争。如果它用了相同的响应,则结果不正确。当A确认无效化消息时,它就好像B得写入完全传播一样。最终的结果是,对于目录,A的读取请求发送在B节点的写入请求之前,而对于节点A,B的写入请求发送在其读取请求之前。
如何才能纠正这种情况呢?蛮力法只是为了避免重叠处理对块的多个请求。这可以通过串行化根节点上的请求处理来实现。缺点则是使之后请求的延迟显著延长。
重叠请求处理方法:
为了确定目录中有多少需要处理的请求可以重叠,首先我们需要能够区分两个请求的处理何时重叠,何时不重叠。换句话说,我们需要知道请求处理的开始和结束时间。当前请求处理的结束时间大于下一个请求的开始时间,则它们的处理是重叠的;否则不是。
首先考虑对单个内存块的请求中那个时更早的请求,那个是稍后的。在基于总线的多处理器系统中,总线提供了确定两个请求顺序的媒介。由于所有读缺失和写入共享块必须到达总线,所以一个请求将比另一个更快地授予总线,因此总线访问命令提供了两个请求顺序一致的试图,如所有处理器看到的顺序。但是在目录多处理器中,没有介质被所有处理器共享并可见。请求首先需要从请求者发送到保存目录的根节点。由于请求可以从不同节点生成,因此实际上不可能防止不同节点同时发出的请求。因此,发出请求的时间不能被用于对请求进行排序。但是,由于所有请求最终都会到达单个节点(根节点),所以根节点可以“决定”到单个内存块的请求顺序。
关于确定请求处理何时开始以及何时结束,结论是:请求处理再目录接收请求时开始,而不是在请求者发出请求开始。读请求的处理过程如图示:
重要的问题是,从处理请求开始,处理何时才算完成?一个直观的答案是,处理中涉及的所有节点都收到了它们应该收到的消息,并且它们已经通知根节点其已收到了这样的消息,我们称之为已根节点为中心的解决方案,因为根节点在最后决定请求处理何时完成。缺点则是处理周期长。在考虑处理完成之前,根节点必须等到请求者发送相关消息的确认消息。同时,它不能服务新的请求,如此漫长的处理周期和串行化可能会严重降低性能。因此,需要一个新的方案使处理周期最小化。
另一种方案是使用请求者的帮助来确保请求处理的顺序。请注意,如果请求者的高速缓存一致性控制器没有修改,则图10.5中的协议竞争将发生,而不是使用此方案。在请求者处,我们现在必须要求一致性控制器维护一个未决事务缓冲区,它跟踪节点发送单尚未收到响应的请求。当发出的请求是目录决定处理的第一个请求,则该节点可能会收到根节点的数据回复。否则,如果该目录已经处理另一个请求,则它推迟处理该请求,而请求者会收到一个NACK消息。因此,请求者必须等待直到回复(数据或NACK)到达。如果不是这样的回复,它会得到另一种类型的消息,那么可以得出结论:已经有一个协议竞争。在接收到数据应答或NACK之前,它不应该处理这样的消息。因此,它必须保留一个缓冲区,以保存无法马上服务的消息。缺点则是在每个节点的缓存一致性控制器引入额外的硬件成本,但它使根节点能够以根节点为中心的方案更快地完成请求处理。
为了在几种情况下处理较长的处理周期,目录状态需要用瞬态来增强。需要一种状态来处理处于EM状态的块的读取。由于最终目录状态是共享的,因此我们将次瞬态状态称为EM2S。需要另一个状态来处理对处于EM状态的块的读取独占请求。由于初始状态是EM,最终状态也是EM(但是具有不同的所有者),所以将此状态称为EMa2EMb。新的目录式一致性协议状态图得到一个更完整的协议:
在图中,瞬态状态以矩形显示。虚线显示进入和离开瞬态状态的新转化,触发这种转换的事件显示未虚线标签。为了减少混乱,其他状态转换的标签不显示(在图10.4中已经显示)。
可能发生的协议竞争的类型也取决于互联网络的属性。如果网络保证一个节点向同一个目标节点发送的两个消息的到达顺序与它们的发送顺序相同,则协议竞争的情况会更少。但是,由于目录中缓存状态的不同步视图而导致的竞争依然存在,需要正确处理。
10.4.3 写传播和事务串行化
基于无效化的目录一致性正确性的原因。支持一致性协议有两个要求:写传播和事务串行化。
与基于总线的多处理器一样,使用无效化协议协议可以简单地实现写传播。当处理器想要写入一个块时,它会向Home发送一个ReadX或Upgr请求,作为响应Home向所有其他共享者发生无效化消息。这迫使他们在将来尝试访问该块时遇到缓存缺失。另外,在最近失效的块高速缓存缺失时,目录通过查找其状态和共享向量来找到该块的最新副本,并且如果该块是脏的,则Home会请求具有该块的处理器,并将该块提供给请求处理器。
事务串行化是避免重叠请求处理问题的一个子集,在这种情况下,它涉及处理两个请求,则其中至少有一个请求是写请求。采用以根节点为中心的方法,在根节点处完全避免重叠。采用请求者协助的方式,通过根节点和请求的合作避免重叠。
10.4.4 同步支持
DSM系统中的同步支持与基于总线的多处理器中的同步支持类似,它至少包含某些原子级内存操作,这些操作是在一致性协议之上实现的。
目录很容易支持原子操作,以避免因为瞬态造成的重叠的请求处理。例如,考虑一恶搞LL/SC指令对的系统。为了实现原子级读写修改序列,节点可以向该目录发送特殊请求。收到次请求后,目录可以使所有共享者无效并进入瞬态。在此期间,所有对该块的新请求(包括由于原子级操作导致的请求)都被否定确认。当请求处理器收到所有无效确认消息时开始执行操作。操作完成后,它可以发送另一个消息回Home,表示其操作已完成。收到该消息后,目录知道该操作已完成原子级操作,因此目录状态可以从瞬态转换为EM状态,并可以服务新的请求。
有趣的是,DSM系统可以通过在本地(内存中)执行原子级操作来减少无效化次数。为了实现这一点,首先同步变量必须放置在不可缓存的页面中,以避免节点缓存他们。然后,节点执行的原子级操作会向目录发出一条特殊消息,其中包含要执行的操作类型和要执行的操作的地址。该目录可以获取该块,执行该操作并将该块放回内存中。如果有多个这样的请求,目录一次一个地执行他们。这种内存中的同步的缺点是在低竞争的情况下,可缓存的同步变量可能表现更好。而且,当同步变量具有很强的时间局部性时,例如当一个线程重复获取兵释放锁而不受其他线程的干预时,可缓存的同步变量将表现得更好。
但是,如果没有内存中的原子级操作,ABQL将产生比其他高度竞争锁定方法更少的流量,代价是更高的无竞争同步延迟。
最后,随着DSM规模的增加,集中式栅栏实现会变得非常昂贵,这既是由于必须无效化的节点数量的增加,也是由于无效化消息必须经过的节点之间距离的增加。因此,一个特殊的栅栏网络变得更具吸引力。
10.4.5 存储一致性模型
为了支持顺序一致性SC,从节点发出的存储器访问必须以严格的程序顺序发布和完成,即读写(到任何地址)的开始必须在前一个读写(到任何地址)完成之后。读取的完成仅仅是当需要读取的数据从缓存返回到处理器时;当所有共享者都发送确认收到无效化消息时可以检测到写入完成。由于请求者最终必须被告知写入完成(以便它可以写入块并开始处理后续请求),因此在请求者处直接收集失效确认消息是有意义的,而不是在根节点收集完成后再向请求者发送消息。
松弛一致性模型通过允许内存访问重叠并在程序顺序之外发生来提高SC性能。DSM系统越大,松弛一致性模型相对于SC优势越大,这是由于处理请求的延迟更高。由于节点之间的距离较长导致延迟更大,因此通过重叠执行来隐藏他们更为重要。
10.5 当前设计问题
10.5.1 处理不精确的目录信息
传统的目录式一致性协议的关键问题之一是陈旧的信息,许多目录条目没有关于那些高速缓存保留了块的副本的最新信息。更具体地说,目录的共享向量字段可能指示某块被高速缓存存储,即使高速缓存很久以前已经替换出该块。出现此问题的原因是干净(未修改)的缓存块通常会从缓存中“悄悄”逐出,而不通知目录。然而,通过通知每个干净块被替换出的消息给目录来解决问题是昂贵的,因为从缓存中逐出的大多数块是干净的,所以它会导致流量的大幅度增加。因此,存在一个基本难以调和的重要权衡。
陈旧的目录信息的第一个问题是用于查找数据块的附加延迟和功耗。当遇到读缺失时,目录可以要通过三种选择来响应:
1. 目录假定其信息已经过时并从内存控制器中获取数据块。缺点是如果数据是可用的,那么从片外存储器中获取浪费大量功耗和延迟。
2. 让目录假定其信息是有效的,并将请求转发给它认为正在缓存该块的片。缺点是数据可能是陈旧的,错误的。
3. 通过执行前二者操作进行对冲。
第二个问题是不必要地占用大量的存储开销,因为一些不再需要的目录项(因为相应的内存块不再被缓存)占用了目录的空间。
第三个问题是基于陈旧的信息,目录条目的替换策略次优地执行。如果目录不允许溢出,则当出现条目被替换时,该块必须从共享者列表指示的所有缓存中进行无效化。由于失效可能导致未来的高速缓存缺失,所以一个好的替换策略可能是选择共享者数量最少得“受害者”条目。不幸的是,由于信息陈旧,具有最少共享者的条目可能对应于仍被缓存的块。
第四个问题是过度的无效化流量。发往目录的写入请求必须假定目录记录的所有可能保留块的副本的节点都失效。如果该目录具有最新消息,即所有或一些节点不再保留该块的副本,则发送到这些节点的失效消息可能已经被免。
对于陈旧的目录信息问题有几种可能得解决方案。一种是即使干净的块被替换时也会发送通知消息。但是,这会大幅增加流量。为了减少通信开销,可以将几个块的替换通知消息合并为单个消息。
陈旧的目录信息的另一个可能的解决方案是在目录中使用一个预测机制来预测一个条目何时可能已经过时。原理是,从t时刻开始,当特定LLC的缓存未命中数量超过其大小时,那么很可能在任何时间访问的目录块将会陈旧,因为涉及的块可能已从缓存中删除。但由于预测不能保证在任何时候都是正确的,所以失效消息仍然需要发送给(可能是过时的)共享者。
10.5.2 一致性粒度
假设目录的一致性以高速缓存块大小的粒度跟踪,通常为64或128字节。这使得一致性管理单元和高速缓存管理单元一致,从而使一致性管理更加简单。但也可以减少或增加由高速缓存一致性协议跟踪的数据大小。降低一致性管理单元大小需要将高速缓存块分成子块,其中每个子块都具有其自己的状态。这增加了目录空间开销并使协议复杂化,但具有减少假共享的潜在益处。但是,增加一致性管理单元的大小更容易做到,而且存在很好的先例。
对于广播式一致性协议,跟踪区域/页级别的一致性的好处是避免广播或侦听。例如,假设一个缓存以独占状态获得了一个区域,但该页只有少数几个数据块保留在缓存中。当缓存在该页面的块中未命中时,可以将非广播或侦听读取请求直接发送到外部缓存或内存。对于目录协议,避免广播不是一个优势,因为该目录已经具有避免广播的作用。
总体而言,采用更大力度的大量数据可提高缓存一致性协议的效率和性能,这些数据具有统一的访问和共享模式,而其余数据则由块级协议处理。
10.5.3 系统划分
随着芯片内核数量的增长,服务器整合功能日益增强,该类系统的特点是需要进行性能隔离(即隔离一台服务器与其他服务器使用的资源),数据共享主要发生在服务器内而不是跨服务器。考虑到这些特点,如果分区内的通信相比跨分区的通信可以更便宜,那么为多核分区提供一种机制是有意义的。
其中每个分区都有自己的“第一级”目录协议,还有另一个“第二级”目录协议来保持跨分区的一致性。一个块有两个根节点:一个根节点在分区中动态分配(当分区发生变化时),另一个根节点分配给第二级目录。将块地址映射到根节点是通过查表来实现的。分区更改时,可以通过向该表写入新条目来重新分配根节点。
10.5.4 加速线程迁移
对于两级高速缓存一致性协议,另一个有趣的可能性是共享者的虚拟化,其中目录协议可以跟踪虚拟共享者而不是单个高速缓存。虚拟共享者可以映射到高速缓存,但不一定是一对一的方式。虚拟化共享者的优点在于:
1. 可能的用途是想要加速线程迁移。
2. 定义分区。每个虚拟共享者都可以表示一个分区,并允许在分区内灵活地移动和复制数据,而不涉及目录,只要数据块的状态允许这样的移动或复制。此外,每个分区可以根据其需要实现不同种类的第一级一致性协议。小分区实现广播式一致性协议,大分区实现目录式协议。