Coherency and Consistency
本章出现的Coherency指Cache Coherency,Consistency指Memory Consistency。许多工程师经常混淆这两个概念,没有建立足够准确的Memory Consistency概念。Consistency与Coherency之间有一定的联系,所关注的对象并不等同。
Memory Consistency的实现需要考虑处理器系统Cache Coherency使用的协议。除了狭义Cache之外,在处理器系统中存在的广义Cache依然会对Memory Consistency模型产生重大影响。Memory Consistency和Cache Coherency有一定的联系,但是并不对等。这两部分内容相对较为复杂,可以独立成篇。有些学者认为Cache Coherency是Memory Consistency的一部分[55],更为准确的说是Memory Coherency的一部分。
我们首先给出Memory Coherency的定义。Memory Coherency指处理器系统保证对其存储器子系统访问Correctness。我们并不关注对处理器私有空间的存储器访问,仅考虑共享空间的这种情况,即便在这种情况下定义Correctness依然很困难。
在一个Distributed System中,共享存储器空间可能分布在不同的位置,由于广义和狭义Cache的存在,这些数据单元存在多个副本;在Distributed System中,不同处理器访问存储器子系统可以并发进行,使得Memory Coherency层面的Correctness并不容易保证。
我们假设在一个Distributed System中含有n个处理器分别为P1~Pn,Pi中有Si个存储器操作,此时从全局上看可能的存储器访问序列有(S1+S2+…+Sn)! / (S1! ×S2!×…×Sn)种组合[56]。为保证Memory Coherency的Correctness,需要按照某种规则选出合适的组合。这个规则被称为Memory Consistency Model,也决定了处理器存储器访问的Correctness。这个规则需要在Correctness的前提下,保证操作友好度的同时,保证多处理器存储器访问较高的并行度。
在不同规则定义之下,Correctness的含义并不相同,这个Correctness是有条件的。在传统的单处理器环境下,Correctness指每次存储器读操作所获得的结果是Most Recent写入的结果。在Distributed System中,单处理器环境下定义的Correctness,因为多个处理器并发的存储器访问而很难保证。在这种环境下,即便定义什么是Most Recent也很困难。
在一个Distributed System中,最容易想到的是使用一个Global Time Scale决定存储器访问次序,从而判断Most Recent,这种Memory Consistency Model即为Strict Consistency,也被称为Atomic Consistency。Global Time Scale不容易以较小的代价实现,退而求其次采用每一个处理器的Local Time Scale确定Most Recent的方法被称为Sequential Consistency[56]。
与Sequential Consistency要求不同处理器的写操作对于所有处理器具有一致的Order不同,Causal Consistency要求具有Inter-Process Order的写操作具有一致的Order,是Sequential Consistency的一种弱化形式。Processor Consistency进一步弱化,要求来自同一个处理器的写操作具有一致的Order即可。Slow Memory是最弱化的模型,仅要求同一个处理器对同一地址的写操作具有一致的Order[56]。
以上这些Consistency Model针对存储器读写指令展开,还有一类目前使用更为广阔的Model。这些Model需要使用Synchronization指令,这类指令也被称为Barrier指令。在这种模型之下,存储器访问指令被分为Data和Synchronization指令两大类。其中Synchronization指令能够Issue的必要条件是之前的Data指令执行完毕,其他指令在Synchronization指令执行完毕前不能进行Issue。在Synchronization指令之间的存储器访问需要依照处理器的约束,可以Reordered也可以Overlapped。
在这种Model中,Data指令的Order并没有受到关注,所有规则仅针对Synchronization指令起作用,也因此产生了Weak Consistency,Release Consistency和Entry Consistency[55][56]三个主要模型。这些模型将在下文做进一步的说明。
对于不支持Network Partitions的Distributed System,可以在实现Strict Data Consistency的同时实现Availability[1]。而对于一个较大规模的Distributed System中,Network Partitions是一个先决条件。例如在一个大型系统中,所使用的Web服务器和数据库系统已经分布在世界上的很多角落,Network Partitions已经是一个事实。
Consistency,Availability和Partition-Tolerance三者不可兼得[57],这使得在一个Network Partitions的Distributed System中,必须在Consistency和Availability之间进行Trade-Off,也引出了Eventually Consistent模型[58]。这种模型是另一种Weak Consistency Model,基于一个数据在较长时间内没有发生更新操作,所有数据副本将最终一致的假设。
Eventually Consistent在DNS(Domain Name System)系统中得到了较为广泛的应用,也是Distributed Storage领域的用武之地。这些内容在Cloud崭露头角之后迅速成为热点,却不是本篇重点。我依然相信在Cloud相关领域工作的人必然可以在处理器存储器子系统的精彩中获得进一步前进的动力,可能是源动力。
这些内容超出了本书的覆盖范围,我们需要对Cache Coherency做进一步说明。从上文中的描述可以发现Memory Consistency关注对多个地址进行的存储器访问序列;Cache Coherency单纯一些,关注同一个地址多个数据备份的一致性。不难发现Cache Coherency是Memory Coherency的基础。
Cache Coherency要求写操作必须最终广播到参与Cache Coherency的全部处理器中,即Write Propagation;同时要求参与Cache Coherency的处理器所观察到的对同一个地址的写操作,必须按照相同的顺序进行,即Write Serialization。
Write Propagation有Invalidate-Based和Update-Based两种实现策略。Invalidate-Based策略的实现首先是确定一次存储器访问是否在本地Cache Hit,如果Hit而且当前Cache Block状态为广义的Exclusive/Ownership,不需要做进一步的操作;否则或者在Cache Miss时需要获得所访问地址的Exclusive/Ownership。此时进行存储器访问的CPU向参与Coherency的所有CPU发送RFO(Read for Ownership)广播报文,这些CPU需要监听RFO报文并作出回应。
如果RFO报文所携带的地址命中了其他CPU的Cache Block,需要进一步观察这个Cache Block所处的状态,如果这个Cache Block没有被修改,则可以直接Invalidate;否则需要向发出请求的CPU回应当前Cache Block的内容,在多数情况下,被修改的Cache Block只有一个数据副本。这种方法在Share-Bus的处理器系统中得到了最广泛的应用,如果存储器访问连续命中本地Cache,命中的Cache Block多处于Exclusive状态,不需要使用RFO报文,因此不会频繁地向处理器系统发出广播操作,适合Write-Back方式。
Update-based策略的实现通常使用Central Directory维护Cache Block的Ownership,在Cache Block Miss时,需要Write Update其他CPU Cache Block存在的副本,可以视网络拓扑结构同时进行多个副本的同步,即便如此所带来的Bus Traffic仍较严重,适用于使用Directory进行一致性操作的大型系统。如果进一步考虑实现细节中的各类Race Condition,完成这种方式的设计并不容易。
除了Invalidate和Update-Based策略之外,Cache Coherency可以使用Read Snarfing策略。在这种实现方式中,可以在一定程度上避免再次Read被Write-Invalidate的Cache Block时,引发的Miss。当一个CPU读取一个Data Block时,这个读回应除了需要发给这个CPU之外,还需要更新其他CPU刚刚Invalidate的Cache Block。在实现中,其他CPU可以监控这个读回应的地址与数据信息,主动更新刚刚Invalidate的数据拷贝[59]。
在参考文献[59]的模式中,使用Read Snarfing策略可以减少36~60%的Bus Traffic。但是这种方式的实现较为复杂,目前尚不知在商业处理器是否采用过这样的实现方式。在学术领域,Wisconsin Multicube模型机曾经使用过Read Snarfing策略[61]。
Write Serialization的实现需要使用Cache Coherent Protocol和Bus Transaction。类似RFO这样的广播报文必不可少。在使用Share Bus和Ring-Bus互连时,较易实现Write Serialization。Directory方式在对同一个地址Cache Block的并发写时需要使用额外的逻辑处理ACK Conflict,这些逻辑大多设置在Home Agent/Node中。
[1] Availbility指来自任何一个处理器的读写请求一定可以获得Response。
3.1 Cache Coherency
Cache Coherency产生的原因是在一个处理器系统中,不同的Cache和Memory可能具有同一个数据的多个副本,在仅有一个数据副本的处理器系统中不存在Coherency问题。维护Cache Coherency的关键在于跟踪每一个Cache Block的状态,并根据CPU Core的读写操作及总线上的相应Transaction,更新Cache Block的状态,借此维护Cache Coherency。Cache Coherency可以使用软件或者硬件方式保证。
在使用软件方式维护时,CPU需要提供专门的显式操作Cache的指令,包括Cache Block Copy,Move,Eviction和Invalidate等指令,多数微架构都提供了这样的指令,如PowerPC处理器设置的dcbt,dcbf,dcba等指令[43]。
程序员可以使用这些指令,维护处理器系统的Cache Coherency。在进行DMA操作之前,可以将数据区域与主存储器通过软件指令保证Cache Coherency,进行DMA操作时,不需要硬件来维护Cache Coherency。在某种情况下,使用这种方式可以提高数据传送效率。
软件维护Cache Coherency的优点是硬件开销小,缺点在多数情况下对性能有较大影响,而且需要程序员的介入。多数情况下Cache Coherency由硬件维护。不同的处理器使用不同的Cache Coherency Protocol实现Cache Coherency。这些Protocol维护一个有限状态机FSM(Finite State Machine),根据存储器读写指令或者Bus Transaction,进行状态迁移和相应的Cache Block操作,隐式保证Cache Coherency,不需要程序员的介入。
根据Cache Coherency Protocol维护Cache Block状态方法的不同,处理器可以使用Bus Snooping和Directory这两大类机制。这两类机制的主要区别在于Directory机制全局统一管理不同Cache的状态;而在Bus Snooping机制中,每个Cache分别管理自身Cache Block的状态,并通过Interconnection进行不同Cache间的状态同步。
无论采用哪种机制,Coherency Protocol所要求的Cache Block操作都可以通过Invalidate,Update或者Read Snarfing策略完成。Update和Read Snarfing策略需要更多的总线操作,对带宽和延迟都有很大影响,多数Cache Coherency Protocol采用了Invalidate策略。
最为经典的总线监听协议Write-Once[60]由James Goodman于1983年提出,是在x86,ARM和Power处理器中大行其道的MESI Protocol(也叫Illinois Protocol)的前身和一种变体,或者说是一种具体实现。
Write-Once Protocol的实现关键在于其使用的特殊的Cache回写机制,即Write Once。Write-Once是Write-Back和Write-Through的综合。当使用这种机制时,对一个Cache Block进行第一次回写时,采用Write-Through策略将数据同时回写到Cache和主存储器,之后的写操作采用Write-Back,只回写到Cache而不回写到主存储器。
这种设计有利于在带宽和Coherency Protocol的复杂度之间取得均衡。Write-Back机制能够有效节约带宽,但是由于主存储器中并没有最新的数据副本,增加了维护Cache Coherency的开销。Write-Through则相反。有关Write-Back和Write-Through的详细信息可以继续阅读本篇的后续章节。
通过之前的描述可以发现,在任何一种Cache Coherency Protocol中,每个Cache Block都有自己的一个状态字段。而维护Cache Coherency的关键在于维护每个Cache Block的状态域。Cache Controller通常使用一个状态机来维护这些状态域。
使用Write-Once回写机制实现Cache Coherency时,每一个CPU Core中的Cache Block需要设置4个状态位,用以识别当前Cache Block的状态,处于这些状态的Cache Block在收到不同的输入后,将进行状态迁移,以保证Cache Coherency。
- Invalid位。表示当前Cache Block不含有有效数据,是个无效Cache Block。
- Valid位。表示当前Cache Block的数据为最新,在主存储器中拥有该Cache Block的数据副本。在Eviction时不需要回写主存储器。其他CPU Core的Cache中可能含有该Cache Block的数据。在这个Cache Block中的数据可能与其他CPU Core中的Cache共享,各个共享数据副本都为最新的值,即与主存储器同步。在进行存储器读操作后,Cache Block可能处于该状态。
- Reserved位有效表明该Cache Block中的数据是最新的,在主存储器中拥有该Cache Block的数据副本。在Eviction时不需要回写主存储器。其他CPU Core的Cache中不能含有该Cache Block的数据。这是由第一次对该数据进行写操作所达到的状态,读者可以简单回忆write-once的写回机制。
- Dirty位。表示该Cache Block中的数据是最新的,而且只有该Cache Block拥有这个最新的数据副本,而且与主存储器不同步。主存储器和其他CPU Core的Cache中没有这个数据副本。这是通过对该数据反复进行写操作所达到的状态,读者可以再次回忆Write-Once的回写机制。
根据以上这几种状态,我们简要分析基于Write-Once协议的Cache Coherency Protocol,其FSM描述如图3‑1所示,其中右图是使用Write-Once协议的处理器系统示意图。在该处理器系统中含有两个处理器,而且只有一级Cache,分别为C0和C1,通过共享总线方式与主存储器进行连接。
![3.1 <wbr>Cache <wbr>Coherency 3.1 <wbr>Cache <wbr>Coherency](https://i-blog.csdnimg.cn/blog_migrate/063ef58a9268b3a387c81e1e2ed6b5f3.jpeg)
在学习基于Write-Once策略的Cache Coherency Protocol时,需要将状态机迁移与处理器互联拓扑进行对照。在Write-Once状态机中含有四个状态,分别为Invalid,Valid,Dirty和Reserved。Cache Block处于这些状态时,可以接收到四种输入请求。
这四种输入请求也是不同处理器的Cache Controller所接收到四种读写操作,即状态机中的输入信号,包括总线读BR(Bus Read),总线写BW(Bus Write),处理器读PR(Processor Read)和处理器写PW(Processor Write)。当Cache Controller监听到对某个Cache Block的操作的时候,将根据当前Cache Block的状态进行迁移。
总线读BR表示Cache Controller监听到了来自总线的读操作。该操作的产生原因是处理器读取的数据不在其本地的Cache中,需要查找主存储器或者其他处理器的Cache。如果此时另外一个处理器的Cache含有该数据最新的副本,该处理器的Cache Controller将提供这个最新的副本,即Data Refill,当该数据副本为Dirty时,需要将数据回写到主存储器,并将自身的状态迁移为Valid,表示这个数据被多个Cache共享。
总线写BW表示某个CPU的Cache Block需要回写主存储器。如果总线写BW是第一次对Cache Block进行写操作时产生,此时使用的是Write-Through策略,而不是Write-Back。特别注意的是,Cache Block因为Replacement而回写到主存储器时不会产生总线写BW。
如果某个处理器的Cache Controller监听到了总线写BW,并且在自身Cache中含有数据的一个副本。此时该Cache Block处于Valid,Reserved或者Dirty状态,首先需要向发起总线写的处理器提供这些数据,即进行Data Refill操作,之后Invalidate这个Cache Block中的副本,并将状态迁移到Invalid。
处理器读PR是处理器读取本地Cache时产生的Transaction。如果本地Cache没有命中,或者处于Invalidate状态,将通过Data Refill操作获得相应的数据,此时该Cache Controller将发出总线读BR,从其他CPU Core的Cache中获得数据副本,之后将状态迁移到Valid。如果此时Cache Block为Valid,Reserved或者Dirty状态时为Cache命中,维持原状态不变。
处理器写PW是处理器向本地Cache写数据时产生的Transaction。在Write-Once策略使用的特殊回写机制中,如果处理器是第一次对该数据进行写操作,该Cache Block状态将迁移为 Reserved,表明只有当前Cache Block和主存储器拥有数据副本。
此时Cache Controller将使用总线写BW Invalidate其他处理器中的数据副本。如果是对该数据的后续写操作,则只写回Cache,从而使状态迁移为Dirty,表明只有当前本地Cache含有该数据副本,此时不会产生BW信号。另外需要注意的是,如果某个数据因为某种原因被替换到主存储器,之后又被再次读入Cache时,对它的写操作也相当于第一次,相当于维护对同一个数据的新的Cache Coherency周期。
除了从Cache Controller的角度分析这些状态迁移之外,还可以从处理器的角度认识Write-Once机制。处理器访问Cache时,可能会出现Read Miss,Read Hit,Write Miss和Write Hit这四种情况。我们分别讨论这四种情况。
如果发生Read Hit,命中的Cache Block一定处于Valid,Reserved或者Dirty状态。Read Hit不会改变该Cache Block的状态。如果发生Read Miss,则该Cache Block的当前状态或者是Invalid或者不在本地Cache中。此时Cache Controller会产生一个总线读BR,从其他处理器的Cache获得所需的数据,该数据会同时存在于主存储器中。此时将Cache Block状态将迁移为Valid,表示此时Cache Block中的数据和主存储器一致,并且其他处理器的Cache可能拥有该数据的副本。
如果发生Write Hit,则根据该写操作是第一次还是后续写,将状态分别迁移到Reserved或者Dirty状态。如果是第一次写操作,进行Write-Through操作的同时也会产生总线写BW,以保证本地Cache中的数据是除主存储器之外唯一的副本。如果发生Write Miss,则该Cache Block的当前状态或者是Invalid或者不在本地Cache中,这次写操作一定是第一次写,因此将迁移为Reserved。
Write-Once协议的实现要点在于第一次写操作采用Write-Through,并通过Write-Through产生的总线写BW,Invalidate其他处理器Cache中的相应数据副本,使本地Cache中的数据成为除了主存储器以外的唯一副本,从而维护Cache Coherency。
MESI Protocol,也被称为Illinois Protocol[66],是大多数SMP处理器维护Cache Coherency采用的策略,其实现与Write-Once Protocol大同小异,虽然这种Protocol出现得相对较晚,却得到了更广泛的应用。目前主流的处理器,如x86,Power,MIPS和ARM处理器,均使用类MESI协议维护Cache Coherency。
MESI Protocol的得名源于该协议使用的Modified,Exclusive,Shared和Invalid这四个状态,这些状态对应Write-Once协议使用的Dirty, Reserved, Valid, Invalid四个状态,所表达的含义也大致相同,但是仍然有些微小区别。标准的MESI Protocol还有一些变种,如MOESI和MESIF等一系列协议。
值得额外关注的是,一个Cache Block除了要使用MESI这些基本的状态位之外,还包含许多辅助状态位,共同维护Cache Coherency。考虑多级Cache间的联系和各种Race Condition的处理情况,MESI Protocol的实现比想象中难出许多。在标准Illinois Protocol中定义了以下四种状态。
- M(Exclusive-Modified)有效表示当前Cache Block中包含的数据与主存储器不一致,而且仅在当前Cache中有正确的副本。
- E(Exclusive-Unmodified)有效表示当前Cache Block中的数据在当前Cache及主存储器中一致。M和E状态都表示Exclusive状态,一个Dirty,一个Clean。
- S(Shared-Unmodified)有效表示当前Cache Block中的数据至少在当前Cache及主存储器中有效,在其他处理器的Cache中也可能含有正确的副本。
- I(Invalid):当前Cache Block无效,不包含有效数据。
Illinois protocol和Write-Once协议的最大不同在于回写机制。Illinois Protocol在写命中时的策略只有Write-Back,而Write-Once区分第一次写和后续写。在使用Write-Once协议时,第一次写时采用Write-Through策略非常重要,正是通过第一次Write-Through产生的总线写BW Invalidate其他副本从而维护Cache Coherency。
在使用Write-Back策略的Illinois Protocol中,一个处理器进行Cache写操作时,将主动广播一个Invalidate Transaction,也被称为RFO(Request For Ownership)。Illinois Protocol根据Write Miss与Write Hit两种情况,将Write-Once协议使用的总线写BW,分解为BRI(Bus Read with Invalidate)和BI(Bus Invalidate Observed)两类Invalidate信号。下面着重分析状态机中与BRI和BI相关的状态迁移,其余情况和Write-Once协议的处理较为类似。Illinois Protocol的FSM描述如图3‑2所示。
![3.1 <wbr>Cache <wbr>Coherency 3.1 <wbr>Cache <wbr>Coherency](https://i-blog.csdnimg.cn/blog_migrate/442b42f22ae666f2f94c4b5c704c5636.jpeg)
当Cache Block处于Invalid状态时,如果Cache Controller收到来自总线的信号时,如BR,BRI和BI,将保持原状态不变;如果Cache Controller收到处理器写PW时,将出现Write Miss,此时Cache Controller会广播一个BRI信号,表示需要从其他处理器中读取该数据,进行Data Refill,并且Invalidate其他所有副本,这也是该操作也被称为Bus Read with Invalidate的原因,之后该Cache Block的状态迁移为Modified,表示拥有唯一最新的副本。
当Cache Block处于Shared状态时,如果收到处理器写PW信号,会广播一个BI信号,表示本地Cache有最新的副本,但是在进行写操作前需要Invalidate其他副本,之后该Cache Block的状态将迁移为Modified。如果处于Shared状态的Cache Block收到来自总线的BRI或者BI Transaction时,将Invalidate本地Cache副本,并且状态迁移为Invalid;如果收到总线读时BRI需要向请求段提供该数据的副本以做Data Refill。
当Cache Block处于Exclusive状态时,在处理器系统中的所有Cache中只有本地Cache拥有数据副本,因此不可能收到来自总线的请求BI;如果收到来自总线的请求BRI,需要向请求方提供该数据的副本以做Data Refill,并且迁移至Invalid状态。
当Cache Block处于Modified状态时,在处理器系统中的所有Cache中只有本地Cache拥有数据副本,因此也不可能收到来自总线的请求BI;如果收到来自总线的请求BRI,需要向请求方提供该数据的副本以做Data Refill,并且迁移至Invalid状态。
MESI协议有一个重要的变种,即MOESI。DEC Alpha21264,AMD x86,RMI Raza系列处理器,ARM Cortex A5和SUN UltraSPARC处理器使用了这种协议。基于MOESI协议的FSM描述如图3‑3所示,该模型基于AMD处理器使用的MOESI协议。不同的处理器在实现MOESI协议时,状态机的转换原理类似,但是在处理上仍有细微区别。
![3.1 <wbr>Cache <wbr>Coherency 3.1 <wbr>Cache <wbr>Coherency](https://i-blog.csdnimg.cn/blog_migrate/192ef8bbc2c0cf76c98737f28c6f6c67.jpeg)
MOESI协议引入了一个O(Owned)状态,并在MESI协议的基础上,进行了重新定义了S状态,而E,M和I状态和MESI协议的对应状态相同。
- O位。O位为1表示在当前Cache 行中包含的数据是当前处理器系统最新的数据拷贝,而且在其他CPU中可能具有该Cache行的副本,此时其他CPU的Cache行状态为S。如果主存储器的数据在多个CPU的Cache中都具有副本时,有且仅有一个CPU的Cache行状态为O,其他CPU的Cache行状态只能为S。与MESI协议中的S状态不同,状态为O的Cache行中的数据与主存储器中的数据可以不一致。
- S位。在MOESI协议中,S状态发生了微小变化。当Cache Block状态为S时,其包含的数据并不一定与存储器一致。不存在状态为O的副本时,Cache Block中的数据与存储器一致;存在状态为O的副本时,Cache Block中的数据与存储器不一致。
在MOESI模型中,Probe Read表示主设备从其他CPU中获取数据拷贝的目的是为了读取数据;Probe Write表示主设备从其他CPU中获取数据拷贝的目的是为了写入数据;Read Hit和Write Hit表示当前访问在本地Cache中获得数据副本;Read Miss和Write Miss表示当前访问没有在本地Cache中获得数据副本;Probe Read Hit和Probe Write Hit表示当前访问在其他CPU的Cache中获得数据副本。
我并不喜欢图3‑3的FSM,更倾向使用图3‑1和图3‑2中的描述,下一版将统一使用BR/BW和PR/PW模型。MESI Protocol广泛应用与Bus-Snooping结构,CMP间的Cache Coherency常使用Directory Protocol。
与Bus-Snooping相比,Directory Protocol所带来的Latency较长,但是Bus Traffic较少,下一版会主要基于Stanford FLASH和DASH[64][65]去书写这部分内容。Intel Sandy Bridge EP系列处理器也使用了这种结构,目前没有公开详细实现。AMD的Magny-Cours中也使用了这种方式进行Cache Coherency,但是也没有公开最新的Bulldozer处理器的实现。还有一个适用与Ring-Bus的Token Protocol值得关注,准备下一版书写。目前暂时如此。
[1] 图3‑1来自Yale N. Patt的讲义。
[2] 图3‑2来自Yale N. Patt的讲义。
参考博文:saling的博客 系列专题http://blog.sina.com.cn/s/blog_6472c4cc0102dusv.html