无数经典的体系结构书籍专注于介绍Inclusive。这使得我所接触的毕业生和工程师很少有Exclusive和NI/NE Cache的概念,包括几年前的自己。一些甚至是来自处理器厂商的工程师也对此知之甚少。也许我们早已熟悉了Inclusice这种Cache Hierarchy结构,认为CPU Core访问L1 Cache中Miss后查找L2 Cache,L2 Cache Miss后继续查找其下的层次。而现代CMP处理器在多级Cache的设计中更多的使用着Exclusive或者NI/NE的结构,纯粹的Inclusive结构在具有3级或者以上的Cache层次中并不多见。
在本节中,我们引入Inner和Outer Cache的概念。Inner Cache是指在微架构之内的Cache,如Sandy Bridge微架构中含有L1和L2两级Cache,而Outer Cache指在微架构之外的Cache,如LLC。在有些简单的微架构中,Inner Cache只有一级,即L1 Cache,Outer Cache通常由多个微架构共享,多数情况下也仅有一级。
在有些处理器系统中,InnerCache由多个层次组成,如Sandy Bridge微架构中,包括L1和L2 Cache,这两级Cache都属于私有Cache,即Inner Cache。此时Inclusive和Exclusive概念首先出现在Inner Cache中,之后才是Inner Cache和Outer Cache之间的联系。为便于理解,如果本节约定Inner Cache和Outer Cache只有一级。
InclusiveCache的概念最为直观,也最容易理解,采用这种结构时,Inner Cache是Outer Cache的一个子集,在Inner Cache中出现的Cache Block在Outer Cache中一定具有副本;采用Exclusive Cache结构时,在Inner Cache中出现的Cache Block在Outer Cache中一定没有副本;NI/NE是一种折中方式,Inner Cache和Outer Cache间没有直接关联,但是在实现时需要设置一些特殊的状态位表明各自的状态。
InclusiveCache层次结构较为明晰,并在单CPU Core的环境下得到了广泛的应用。但是在多核环境中,由于Inner Cache和Outer Cache间存在的Inclusive关联,使得采用多级Cache层次结构的处理器很难再使用这种方式。如果在一个处理器系统中,每一级Cache都要包含其上Cache的副本,不仅是一种空间浪费,更增加了Cache Coherency的复杂度。
即便只考虑2级Cache结构,严格的Inclusive也不容易实现。为简化起见,我们假设在一个CMP中含有两个CPU Core。在每一个Core中,L1为Inner Cache,而L2为Outer Cache,Inner Cache和Outer Cache使用Write-Back策略,Core间使用MESI协议进行一致性处理。
我们首先讨论L1 CacheBlock,在这个Cache Block中至少需要设置Modified,Exclusive,Shared和Invalid这4种Stable状态。由于Strict Inclusive的原因,还有部分负担留给了L2 Cache Block。L2 Cache Block除了MESI这些状态位之外,为了保持和L1 Cache之间的Inclusive,还需要一些额外的状态,如Shared with L1 Cache,Owned by L2 Cache,L1 Modified and L2 stale等一些与L1 Cache相关的状态。
如果在一个CMP中,仅含有两级Cache,增加的状态位仍在可接受的范围内,但是如果考虑到L3 Cache的存在,L3 Cache除了自身需要的状态之外,还受到Inclusive的L1和L2 Cache的影响,与两级Cache相比,将出现更多的组合,会出现诸如L1 Modified,L2 Unchanged and Shared with L3这样的复杂组合。
有人质疑采用Inclusive结构,浪费了过多的Cache资源,因为在上级中存在的数据在其下具有副本,从而浪费了一些空间。空间浪费与设计复杂度间是一个Trade-Off,在某些场景之下需要重点关注空间浪费,有的需要关注设计复杂度。
通常只有OuterCache的容量小于Inner Cache的4倍或者8倍时,空间浪费的因素才会彰显,此时采用Exclusive Cache几乎是不二的选择。而当Outer Cache较大时,采用Inclusive Cache不仅设计复杂度降低,这个Inclusive Outer Cache还可以作为Snoop Filer,极大降低了进行Cache Coherency时,对Inner Cache的数据访问干扰。
采用InclusiveCache的另一个优点是可以将在Inner Cache中的未经改写的Cache Block直接Silent Eviction。这些是Inclusive Cache的优点。但是从设计的角度上看,采用Inclusive Cache带给工程师最大的困惑是严格的Inclusive导致的Cache Hierarchy间的紧耦合,这些耦合提高了Cache层次结构的复杂度。
首先考虑OuterCache的Eviction过程,由于Inner Cache可能含有数据副本,因此也需要进行同步处理。采用Backward Invalidation可以简单地解决这个问题,由于Snoop Filter的存在,使得这一操作更加准确。不过我们依然要考虑很多细节问题,假如在Inner Cache中的副本是已改写过的,在Invalidation前需要进行数据回写。
另外一个需要重点关注的是在InclusiveCache设计中存在的Race Condition。无论是采用何种Cache结构,这些临界条件都需要进行特别处理,但是在Inclusive Cache中,Inner Cache与Outer Cache的深度耦合加大了这些Race Condition的解决难度。
我们首先考虑几种典型的RaceCondition。假设Outer Cache由于一个CPU Core存储器操作的Cache Miss而需要进行Outer Cache的Eviction操作。由于Inclusive的原因,此时需要Backward Invalidate其他CPU Core Inner Cache Block,而在此同时,这个CPU Core正在改写同一个Cache Block。同理假设一个CPU Core正在改写一个Inner Cache Block,而另外一个CPU Core正在从Outer Cache Block中读取同样的数据,也将出现Race Condition。
这些RaceCondition并不是不可处理,我们可以构造出很多模型解决这些问题。这些模型实现的相同点是在Inner Cache和Outer Cache的总线操作间设置一些同步点,并在Cache Block中设置一些辅助状态,这些方法加大了Cache Hierarchy协议与状态机的实现难度。解决这些单个Race Condition问题,也许并不困难,只是诸多问题的叠加产生了压垮骆驼的最后一根稻草。如果进一步考虑CMP间Cache的一致性将是系统架构师的噩梦。
这并不是InclusiveCache带来的最大问题。这个噩梦同样出现在Exclusive和NI/NE结构的Cache层次结构设计中,只有每天都在做噩梦还是两天做一个的区别。顶级产品间的较量无他,只是诸多才智之士的舍命相搏。
InclusiveCache绝非一无是处,如上文提及的Snoop Filter,Inclusive LLC是一个天然的Snoop Filter,因为在这类LLC中拥有这个CMP中所有Cache的数据副本,在实现中只需要在LLC中加入一组状态字段即可实现这个Filter,不需要额外资源,Nehalem微架构使用了这种实现方式[12]。
在电源管理领域,使用纯粹的InclusiveCache时,由于Outer Cache含有Inner Cache的全部信息,因此在CPU Core进入节电状态时,Inner Cache几乎可以全部进入低功耗状态,仅仅维护状态信息,不需要对其内部进行Snoop操作。这是采用NI/NE和Exclusive Cache无法具备的特性。
采用ExclusiveCache是Pure Inclusive Cache的另一个极端,从设计实现的角度上看,依然是紧耦合结构。在这种Cache组成结构中,1个Cache Block可以存在于Inner Cache也可以存在于Outer Cache中,但是不能同时存在于这两种Cache之中。与Inclusive Cache相比,Inner和Outer Cache之间避免了Cache Block重叠而产生的浪费,从CPU Core的角度上看,采用Exclusive Cache结构相当于提供了一个容量更大的Cache,在某种程度上提高了Cache Hierarchy的整体Hit Ratio。
在一个设计实现中,Cache的Hit Ratio和许多因素相关。首先是程序员如何充分发挥任务的Temporal Locality和Spatial Locality,这与微架构的设计没有直接联系。而无论采用何种实现方式,对于同一个应用,提供容量更大的Cache,有助于提高Cache的Hit Ratio。
由上文的讨论我们可以轻易发现,在使用相同的资源的前提下,使用Exclusive Cache结构可以获得更大的Cache容量,此时Cache的有效容量是Inner Cache+OuterCache。这是采用Inclusive Cache或者NI/NE无法做到的,也是Exclusive Cache结构的最大优点。这仅是事实的一部分。
假设在一个微架构中,含有两级Cache,分别是Inner和Outer,并采用Exclusive结构。此时一次存储器读请求在Inner Miss且在Outer Hit时,在Inner和Outer中的Cache Block将相互交换,即Inner Cache中Evict的Cache Block占用Outer Cache Hit的Cache Block,而Outer Cache Hit的Cache Block将占用Inner Cache Evict的Cache Block。
如果存储器读请求在Inner和Outer Cache中全部Miss时,来自其下Memory Hierarchy的数据将直接进入Inner Cache。因为Exclusive的原因,来自其下Memory Hierarchy的数据不会同时进入到Outer Cache。
在这个过程中,Inner Cache可能会发生Cache Block的Eviction操作,此时Eviction的Cache Block由Outer Cache接收,此时Outer Cache也可能会继续出现Eviction操作。这些因为Inner或者Outer Cache的Eviction操作而淘汰的Cache Block,也被称为Victim Cache Block或者Victim Cache Line。
在采用Exclusive Cache的微架构中,需要首先考虑Victim Cache Block的处理。当CPU Core进行读操作时,如果在Inner Cache中Miss,需要从Outer Cache或者其下的Memory Hierarchy中获得数据,这个数据直接进入到Inner Cache。此时Inner Cache需要首先进行Eviction操作,将某个Cache Block淘汰。这个Victim Cache Block需要填充到某个数据缓冲中。可以是Outer Cache作为Victim Cache。即淘汰的Cache Block进入Outer Cache,当然采用这种方法,可能继续引发Outer Cache的Eviction操作,从而导致连锁反应。
在采用Exclusive Cache结构的处理器系统中,Outer Cache经常Hit的Cache Block也是Inner Cache经常Evict的Cache Block[84]。这与Wen-Hann有关NI/NE Cache结构的Accidentally Inclusive的结论一致,在NI/NE Cache结构中,虽然Inner Cache与Outer Cache彼此独立工作,但是根据统计在多数时间,在Inner Cache中Hit的Cache Block也存在于Outer Cache。这不是设计的需要,而是一个Accident,Wen-Hann将其称为Accidentally Hit[85]。
[84]和[85]的结果是对同一现象的两个不同角度的观察,这一现象由Inner Cache和Outer Cache的相互关联引发。对于使用Exclusive Cache结构,需要使用某类缓冲存放淘汰的Cache Block,如Victim Replication[88],Adaptive Selective Replication[84]等一系列方式,当然理论界还有更多的奇思妙想。这些内容进入到了比较专业的领域,并不是本篇的重点,本篇仅介绍AMD K7系列的Athlon和Duron微架构的实现方式。