CPU与内存的中转站 ——了解CPU缓存的秘密

转载自 青原的日记

Cache,在现在对大多数DIYer来说都是再熟悉不过的词了,特别是谈论到CPU的时候,不得不提的就是Cache,并且Cache已经与频率一样,成为衡量CPU性能最重要的参数之一了,甚至超过了频率的重要性。而CPU厂商Intel和AMD近年来也是在Cache中作文章,在进行高低端产品划分的时候更是把Cache容量作为最大的划分界限。正是由于这些原因,许多人已经不满足于仅仅知道Cache的容量了,越来越多的人渴望能够了解它的结构和相关技术。


一:Cache的概念,起源与发展
     既然要了解Cache,当然要把什么是Cache搞清楚。Cache最初来源于法语,指的是隐秘的,保存贵重东西的地方,也有临时,暂时的意思。综合两者,对Cache的定义就是——临时的保存贵重东西的隐秘地方。在计算机中,贵重代表着CPU所要用到的数据或者指令是十分重要的;临时的含义是说 Cache只是在CPU工作时存在,而断电以后就消失;隐秘是针对软件层来说的,Cache对软件层来说是不可见的。大家一般把它叫做缓存,说到底,也是一种存储器,和内存这些存储体做着一样的事情,只不过它有着不一样的特性,使得它成为CPU内部的存储体,担着CPU与内存的中转站的责任。在现代计算机中,缓存就是嵌在CPU内部的一些微小,低延迟的存储体。
     时间回到1978年,第一颗在个人PC上使用的微处理器——8088,它的主频才4.77MHz,导致当时CPU的存取时间(800ns左右)远大于内存的存取时间(200ns左右),所以那时候根本不需要Cache。到80386开始,CPU的频率一下提高到40MHz,但是内存的上升速度却没有象 CPU一样快,导致没有相匹配的内存可以使用,使得CPU要耗费几个,十几个时钟周期来等待内存的读写,这显然不能让人接受,于是有两种解决方案同时被提出来:一种是在CPU内加入等待周期,降低CPU的处理能力;而另一种就是寻找一种速度快,面积小,延迟短的存储体来做CPU与内存的中转站,也就是我们现在所说的Cache(缓存)。第一种方法显然是自欺欺人,牺牲CPU的性能来换取整体的平衡,所以第二种方法立刻被采用。不过在386时期,由于成本的问题,并没有内部L1 Cache,只有外部的Cache。而486时代,CPU频率再次增加,外部Cache的速度也要相应提高,使得成本太高,于是内嵌了8K的L1 Cache,同时也可以使用外部的L2 Cache。直到Pentium时代,由于Pentium采用了双路执行的超标量结构,有2条并行整数流水线,需要对数据和指令进行双重的访问,为了使得这些访问互不干涉,于是出现了8K数据Cache和8K指令Cache,并且可以同时读写,不过L2还是外部的,接着出现的Pentium Pro为了提高性能,把L2内嵌了,到此为止,就确定了现代缓存的基本模式了,并一直沿用至今。


二:Cache的工作原理
     以CPU的中转站的“身份”出现的Cache,它是如何履行它中转站的职责的呢?换句话说,缓存的工作原理是怎样的?

     在计算机中,CPU与Cache之间交换数据是以字为单位的,而Cache与内存之间交换数据是以块为单位的,并且在Cache中,是以若干字组成的块为基本单位的。一般情况下,CPU需要某个数据的时候,它会把所需数据的地址通过地址总线发出,一份发到与内存中,一份发到与Cache匹配的相联存储器(CAM)中,CAM通过分析对比地址,来确定所要的数据是否在Cache中,如果在,则以字为单位把CPU所需要的数据传送给CPU,如果不在,则 CPU在内存中寻找到该数据,然后通过数据总线传送给CPU,并且把该数据所在的块传送到Cache中。从此原理可以知道,Cache的作用就是在CPU 与内存中做个中转站,尽可能的让CPU访问自己,而不去访问内存,从而降低延迟,提高效率。大家都知道,CPU的处理能力越来越强,处理数据的频率也越来越快,怎样让有限的容量中,尽可能的提高其命中率呢?这就利用到CPU的两个局限性了——时间局限性与空间局限性。Cache存在和工作原理正是基于这两个局限性。

     时间局限性:如果CPU执行了程序的某个指令,那么很可能在接下来还会执行这个指令;如果CPU访问了某个数据,那么很有可能在接下来的运算中,还会访问这个数据。产生这种局限性的原因是因为很多程序都会经常出现循环操作。比如游戏,在玩游戏时,很多情况是需要同一段代码来对场景进行渲染的,这时,把那段代码放入缓存,CPU就可以用最少的时间来反复访问这段地址,从而提高处理效率。通常我们把CPU在时间局限性中反复访问的那段地址叫做“Working Set”(工作区),依据不同程序的不同工作区,Cache就可以以较小的容量来存储CPU所需要的数据,提高命中率。
     空间局限性:一旦CPU访问了某个存储单元,那么在不久以后,CPU很有可能会访问其附近的存储单元,也就是说,CPU在一段时间内访问的地址很可能是连续的。发生这种情况经常是执行顺序程序。这种情况我们最常见了,比如说听歌,看电影等一些有规律的程序,我们在听歌或者看电影的时候都是按照时间顺序来的,正常情况下,我们看电影看到10分钟的时候,那么接下来肯定是10分01秒,再接下来10分02秒,依次推下去….这种空间局限性对CPU来说是有很大好处,因为它不用跳来跳去到处找下步需要的数据,只要依次在地址内读取就行了;并且程序员在写代码的时候也会尽量避免出现到处跳转的情况,好让CPU可以连续的处理大部分代码,来提高CPU运行此代码的效率。
     这里有两点要说明一下:时间局限性其实在小范围内包括了空间局限性,但反过来则不是了,换句话说就是CPU反复访问的数据或者指令是连续在一起的,而连续在一起的数据或者指令却不一定会反复使用,细心的读者应该从前面的说明看出来了。另一点就是,很多程序在执行指令(代码)和数据时,所呈现出来的局限性是不同的,有可能执行代码的时候呈现出时间局限性,而数据呈现出空间局限性,比如前面举的游戏,在执行代码时,它呈现出了很好的时间局限性,而读取数据时,它呈现出来的可能就是空间局限性了,这也是为什么L1 Cache会分成Instruction Cache(指令缓存)和Data Cache(数据缓存)的原因之一。
正是由于CPU具有这两个局限性,才给Cache的实现提供了理论依据,让它以较小的容量来保证CPU所需要的大部分指令和数据,所以我们才说Cache的工作原理是基于这两个局限性。


三:Cache的组成材料与结构设计
     在现代计算机中,我们最熟悉的半导体存储体有三种:用于存储BIOS信息的EEPROM(Electrically Erasable Programmable Read Only Memory,电可擦写可编程只读存储器),用于存储临时工作数据的DRAM(Dynamic Random Access Memory,动态随机访问存储器)与SRAM(Static Random Access Memory,静态随机访问存储器)。
     EEPROM由于成本过高,速度太慢,肯定不能做为Cache材料的选择,而DRAM和SRAM两种,为什么是SRAM用来做Cache,而不是DRAM呢?当然最大的原因是速度。

     我们知道DRAM目前最常见的应用就是内存了,它是利用每个单元的寄生电容来保存信号的,正因如此,它的信号强度就很弱,容易丢失,所以DRAM需要时时刷新来确定其信号(根据DRAM制造商的资料,DRAM至少64ms要刷新一次,并且由于每次读取操作都会破坏DRAM中的电荷,所以每次读取操作之后也要刷新一次,这就表示,DRAM至少有1%的时间是用来刷新的)。这样,DRAM的存储周期就增大了,当然潜伏期也变大了。我们从Cache的起源可以看出,正是因为内存的读取时间过高而引入Cache的,所以DRAM不符合做Cache的标准。而SRAM不是通过电容充放电来存储数据,而是利用设置晶体管的状态来决定逻辑状态,让其保存数据,并且这种逻辑状态和CPU本身的逻辑状态相象,换句话说,SRAM可以以接近CPU频率的速度来运行(在今年秋季的IDF上,Intel公布的65nm工艺的SRAM,可以运行在3.4GHz的频率上),再加上读取操作对SRAM是不具破坏性的,不存在刷新问题,大大的缩短了潜伏期。所以这种低延迟,高速度的SRAM才是Cache材料的首选。

不过由于SRAM内部采用的双稳态电路的结构,并且利用这种电路来控制晶体管的状态来存储数据,使得SRAM的电路结构非常复杂,往往要采用大量的晶体管来确保寄存器的数量,一般SRAM内部每个单元(保存1bit数据)需要6个晶体管,而DRAM只要1个晶体管,相比之下,SRAM的成本要比DRAM高的多,所以现代计算机系统中,都是用少量的SRAM加上大量的DRAM来使计算机达到性能与成本的平衡。
既然了解了Cache的基本组织单元是SRAM,那接下来我们就来看看Cache是怎么样利用SRAM来实现“CPU中转站”的功能的。
     从前面的文章可以知道,Cache并不是与CPU为一体的,它是一种计算机发展中的产物,不属于CPU内任何一个结构部件,而是一种存储体,但没有独立编址,而是按装入的内容来决定其地址,因为Cache的内容本身就是从内存中读取的,如果独立编址的话,也就固定了Cache只能装入哪些数据了,要换数据的时候只能把所有Cache的内容撤下,这肯定不能让人满意。而且Cache不独立编址,采用与内存相同的地址有个很大的好处就是,程序员写程序的时候就不必考虑这数据是在Cache中还是在内存中,只要全把它当做是在内存中来处理(由于Cache没有地址,所以它的运做在软件看来就是不存在,这就是 Cache隐秘的含义),完全可以不必为Cache来写不同的代码。
     从关于原理的分析中我们知道Cache与内存之间交换数据是以块为单位的(也可以叫做行),每块由若干个定长的字组成。Cache每从内存读取的一串数据都是以块单位存储起来,而每个块的大小一般为16到64个字节,并且每个块都会装入一个框架(block frame)中,相应的块地址装入到CAM中,也一并放入框架中。现代缓存设计中,我们把每个缓存中的CAM叫做Tag ram,也就是说块地址是存储在Tag ram中的。这样每个框架都只会装入一个块和相应的Tag标记,这就是Cache最底层的组织方式。根据Cache的工作原理,在CPU读取数据的整个过程中,Cache控制逻辑就要在瞬间完成以下3个步骤:1:检查CPU所要的字是否在Cache中(由CAM完成)。2:如果在,找出该字所在的块3:最后读出此字,这其中,过程最复杂的就是第2步——如何找出所要的块。所以科学家们在缓存设计时,先后出现了3种组织方式来组织这些块。
     最概念,最简单的组织方式首先被提出来了——完全相联(fully associative)。在这种组织方式中,Cache中的块与块之间,以及存储顺序或者地址之间没有直接的关系,也就是说,内存中的任意块都可以装入 Cache中任意的框架中,而Cache必需对每个块和其地址进行存储。当CPU请求数据时,通过比较Tag标记中的地址来确定所要的块在哪个框架中,这样的设计结果是命中率相当高,但延迟也相当大,特别是在大缓存中,有成千上万的框架,要比较完这些框架的标记会耗去大量的时间,所以这种组织方式比较适合小容量缓存。

     接着出现了与完全相联方式相反的直接映像方式,它在整个过程中,标记只需要比较一次。这种设计的方向就是降低延迟,采用的设计方式对存入缓存块的地址有非常严格的限制,规定内存中哪块的数据在读进缓存以后只能放在那个框架中,而不能保存其它框架中,至于放在哪个框架中,这有个算法——块地址与整个框架数的同余。我们举个简单的例子来看,有个1K的缓存,块大小为64字节,则总共有16个缓存块,也就是有16个框架,那在内存中首地址为12480的内存块应该保存在缓存的哪个框架中呢?12480/64=195,195mod16=3,则它应该放入第4个框架中。这样一来,内存中的数据能很快的读取到缓存中的某个块中,CPU也能很快的在这个块中找到所要的数据,这样就省下了对比各个框架的时间,自然延迟就小了,但是,如果第4个框架中装入了内存块195的数据,而其它同余依然是3的35,51,67等这些块就不能装入了,这样,当CPU需要35,51,67这些块的时候,就会发生冲突(collision),导致出现Cache miss的情况,大大的降低了命中率。

     正所谓“鱼和熊掌不可兼得”,于是科学家们在两种设计方案取得了一个折衷——路组相联映像。它把所有的框架分成多个组,每组再包含多个框架,内存块存放在 Cache的哪一组是固定的,而在组内到底要放在哪一个框架中又是灵活的,可以任意放置,每个组放多少个框架,我们就称它为几路。这种组织方式就是综合了完全相联与直接映像的优点,让数据可以在一定的区域自由存储,而这个区域对整个Cache来说又说固定的,这样就在命中率和延迟上取得了一个较好的平衡。当CPU请求数据时,只要比较组内之间的标志,路数越少,延迟也越小,但命中率就相应降低。继续举前面的例子,在1K的缓存里,块大小为64字节,按照最简单的2路组关联,总是8组,首地址为12480的内存块应该怎么放呢?12480/64=195,195mod8=3,所以应该放入第4组,而在第4组有两个框架,195占用了一个框架,还有一个框架空着,当CPU需要35这个块的时候,内存又可以马上把35放进第4组的另一个框架中,而不要替换掉 195那块,这样的好处就是,当CPU再需要195的时候还可以在同一个地方找到,提高了效率,同理,当需要其它51,67的时候,只要把2路变成4路, 8路即可。

     经过长期的实践,科学家们发现了一些规律:在路组相联中,8路是一个分界点,8路相联与全相联的命中率几乎是一样了,超过8路,它所带来的延迟就要超过它所带来的好处了,而不到2路的相联所带来的冲突也将超过延迟带来的好处。总之,这三种组织方式都有各自的优缺点,路组相联是目前最好的缓存组织方式,但在不同领域的Cache,在设计上也不同,科学家们将根据需要来设计Cache的组织方式。


四:缓存的替换策略和写入策略
    由于Cache容量有限,为了让CPU能及时在缓存中读取到需要的数据,就不得不让内存中的数据替换掉Cache中的一些数据。但要怎样替换才能确保缓存的命中率呢?这就牵涉到替换策略问题了。
在三种缓存设计方式中,直接映像的替换最简单,因为每个内存块对应的缓存位置都是固定的,只要直接把原来的换出即可。但对于全相联和路组相联来说,就比较复杂了,因为内存块在缓存中组织比较自由,没有固定的位置,要替换的时候必须考虑哪些块CPU可能还会用到,而哪些是不会用到。当然,最轻松的就是随机取出一块CPU当时不用的块来替换掉,这种方法在硬件上很好实现,而且速度也很快,但缺点也是显而易见的,那就是降低了命中率,因为随机选出的很可能是 CPU马上就需要的数据。所以,Cache的替换策略出现了以下几种算法:
     先进先出(First In First Out,FIFO),即替换最早进入缓存的那一块。这种算法,在早期的CPU缓存里比较多使用,那时候的Cache的容量还很小,每块内存块在缓存的时间都不会太久,经常是CPU一用完就不得不被替换下来,以保证CPU所需要的下块内存块能在缓存中找到。但这样命中率也会降低,因为这种算法所依据的条件是在缓存中的时间,而不能反映其在缓存中的使用情况,最先进入的也许在接下来还会被CPU访问。
     最不经常使用的块(Least Frequency Used,LFU),即替换被CPU访问次数最少的块。LFU算法是将每个缓存块设置个计数器,起始为0,每被CPU访问一次,就加1,当需要替换时,找到那个计数最小的替换出来,同时将其它块的计数置0。这种算法在一定程度上还是很理想的,它利用了时间局限性原理,但是每次替换完都把其它块置0,使得把这种局限性的时间限定在了两次替换之间的时间间隔内。由于替换太频繁,让这时间间隔太短了,并不能完全反映出CPU近期的访问情况。
     近期最少使用的块(Least Recently Used,LRU),即替换在近段时间里,被CPU访问次数最少的块,它是LFU的基础上实现的。它的原理是在每个块中设置一个计数器,哪块被CPU访问,则这块置0,其它增1,在一段时间内,如此循环,待到要替换时,把计数值最大的替换出去。这种算法更加充分利用了时间局部性,既替换了新的内容,又保证了其命中率。有一点要说明的是,有时候“块”替换出,并不代表它一定用不到了,而是缓存容量不够了,为了让要进去的内存块腾出空间,以满足CPU的需要。这种算法是目前最优秀的,大部分的Cache的替换策略都采用这种算法。
     说了这么多,我们所讨论的都是一个操作——读取, 因为相比于写入操作来说,读取操作是最主要的,并且复杂得多,而写入操作则要简单的多,下面我们就来看看cache的写入策略。
     大家都知道,在一台PC中,任何设备都是从内存读取数据的,所以一当CPU更改了Cache的数据,就必须把新的数据放回内存,使系统其它设备能用到最新的数据,这就涉及到写入了,目前的写入策略有三种:
     写回法:当CPU写Cache命中时,只改变其缓存的内容,而不写入内存,直到替换策略把该块替换出来时才写入内存。这种方法减少了访问内存的次数,缩短了时间,也提高了内存带宽利用率,但在保持与内存内容的一致性上存在在隐患,并且使用写回法,必须为每个缓存块设置一个修改位,来反映此块是否被CPU修改过。
     全写法:当写Cache命中时,立即在所有的等级存储介质里更新,即同时写进Cache与内存,而当Cache未命中时,直接向内存写入,而Cache不用设置修改位或相应的判断器。这种方法的好处是,当Cache命中时,由于缓存和内存是同时写入的,所以可以很好的保持缓存和内存内容的一致性,但缺点也很明显,由于每次写入操作都要更新所有的存储体,如果一次有大量的数据要更新,就要占用大量的内存带宽,而现在PC系统中,内存带宽本来就不宽裕,而写操作占用太多带宽的话,那主要的读操作就会受到比较大的影响。
     写一次法:这是一种基于上面两种方法的写策略,它的特点是,除了第一次写Cache命中的时候要写入内存,其它时候都和写回法一样,只修改缓存。其实这也就是一种对缓存一致性的妥协,使得在缓存一致性和延迟中取的一个较好的平衡。
     其实,在整个CPU中,写操作的次数远远比读操作来的少,再加上CPU速度越来越快,现在写入方面已经不是什么大问题了。上部纯理论说了这么多,接下来笔者就Intel和AMD的主流CPU缓存架构做个简单分析。


一:NetBurst架构的缓存设计
从Willamette开始,到现在双核心Smithfield,NetBurst架构已经走了5年了,并把摩尔定律演义的淋漓尽致,虽然由于功耗问题在 4G面前倒下,以致NetBurst架构虽然被很多人嗤之以鼻,认为它是一个失败的架构,是个频率至上的推动者,但笔者个人认为NetBurst架构是个很优秀的架构,在架构的很多地方都体现出了NetBurst与众不同的地方,并代表现在最先进的技术,在Cache设计上更是如此。我们先来看看 prescott核心的P4 630的CPU-Z截图:


     大家咋一看,会发现Intel真的很“小气”,L1的容量才“16K+12K”,但细细看图,会发现框内的L1 Trace的单位不一样,用的是μOps(也叫做micro-ops),而不是一般的Bytes,那到底micro-ops到底是个什么单位,它和普通的 Bytes有关系吗?要知道这些,我们先来了解NetBurst缓存中最大的特点——trace cache。
大家都知道,现代CISC处理器为了提高效率都借鉴了RISC结构处理器的思想,象采用超标量设计,多级流水线工位等,其中最重要的就是把复杂,长短不一的X86指令转换成CPU内部的微指令来执行。所以一般处理器的执行路径是这样:先经过分支预测得知将要执行的代码,然后通过指令缓存中读取未加工的程序代码,接着识别,分析以及对这些代码进行译码,最后获得一串微指令让CPU来执行。这其中过程最耗时的就是对代码进行译码,以往都是等到CPU需要指令的时候再临时译码,而trace cache的出现改变了这种局面,代码译码被省略了,让trace cache给CPU“代办”了。trace cache是怎样做到的呢?
     原因在于trace cache和普通的指令缓存差别很大,它是一种用来存放按照分支预测获得的动态指令执行顺序排列的微操作的一种高速缓存体系。它工作时,它的控制逻辑电路就会为预测分支路径读取X86指令代码,然后译码成定长,功能简单的微操作指令并存储在一个在trace cache中被称为回溯片断(trace segment)的逻辑组中,并且这些译码后的指令非常灵活,它能够进入trace segment前决定是否直接流入流水线来执行。当trace segment中的微操作指令执行后,trace cache就会马上变成“执行模式(execute mode)”,这种模式下,微指令就会依据预测时的顺序来被CPU提取,并执行。这样一来,Trace cache存储的指令就是CPU可以直接执行的微操作,并且由于是存放在L1中,使得CPU每次需要时都可以马上取得译码好的微指令,再加上超低的延迟可以让CPU执行频率迅速增大,使CPU的主频达到前所未有的高度。还有一个重要的好处就是可以让CPU本身的X86解码单元减少,简化了结构。总的来说, Trace Cache引入的最终目的就是为了减少X86指令解码器,缓解长流水线出现预测失误所带来的性能损失,并代替原始的L1指令缓存。不过到现在为止, Intel也没有公布Trace Cache的大小,只是表示能够塞进12K的微指令,但是Trace Cache拥有比传统指令高速缓存更复杂的控制、预测电路,而且译码后的微指令会迅速膨胀,因此12K微指令所占用的空间不会比普通处理器来的少,我们从 prescott的内部核心图就可以看出来。





     我们再来看看它的L2设计,Intel的CPU在L2的设计上,一贯是很大方的,比如这款prescott核心的L2,就提供了2M容量,其组织方式采用了8路组相联(Associativity),块大小(Line Size)为64Bytes,由此可以看出,Intel对L2的命中率有比较大的要求,但又提供了256bit的带宽,来确保在一定程度上控制延迟。
从上面的叙述来看,Intel CPU的缓存设计方向是以trace cache+大容量的二级缓存,其二级缓存大的原因就是为了提高其命中率,因为NetBurst一出现“Cache miss”现象所带来的性能损失是很严重的,大家从Northwood Celeron的表现就可以看出来,其性能差的最大原因就是128K的L2,并且只采用了2路组相联。


二:K8的缓存设计

     相比于NetBurst的缓存,K8的缓存设计显得中规中矩,我们先来看L1的设计。
K8的L1还是按照K7一样,采用了L1指令缓存+L1数据缓存,并没有象Intel一样引入trace cache,虽然这种设计没有Intel先进,但64K+64K的容量保证了其命中率,而2路组相联又控制了延迟,其中L1数据缓存和其他通用处理器的数据缓存没有多大区别,都是存储CPU需要用到的数据,而L1指令缓存就不同了,它的功能和NetBurst的trace cache有点相似,但是没有trace cache那么优秀,而且过程也更复杂。从前面对P4架构的分析我们知道,trace cache存储的是译码过的微指令,而在K8中,L1指令缓存存储的是微指令的信息,而不是微指令本身。K8在执行指令的时候,会先对需要的X86指令进行分析和选择,然后把分析得出的信息保存到一个解码阵列中,每条指令的信息大概占3bit的位置,每条指令的信息只存储一次,K8的L1指令缓存就是保存这些解码阵列,也就是保存这些指令信息。K8根据存储的信息把每条X86指令转换成1~2条“宏操作”并执行,由于这种“宏操作”都是定长的,而且结构统一,而且每个通道可以执行2条,再加上K8是3条流水线并行的超标量设计,因此可以同时执行6条这样的宏操作,正是因为如此,AMD才能以2.4GHz的 CPU对抗INTEL高达3.4GHz的CPU。有此可见,K8的L1指令缓存设计的优越性了吧。从L1的设计可以看出来,K8的设计理念和P4还是有点相似的,都是通过把X86指令转换成定长的微指令来提高CPU的执行效率,只不过P4把它进一步发展了而已。
     接下来,我们再看L2,其实K7开始,L2就有些很有意思的特点,而 K8也一样。大家都知道,在一般的CPU缓存设计上,靠近核心的缓存会在下一级缓存有个映射,也就是说,一级缓存的内容一般都可以在二级缓存中找到,而二级缓存又会存在在三级缓存中,这样的结果是,越远离核心的缓存一定要比靠近核心的大。不过大家可能已经发现,从K7的Duron到K8的754 sempron,一些型号的CPU的二级缓存却没有比一级缓存大,而是一样大,甚至Duron的还更小,这是为什么呢?其实AMD历来在设计缓存时,都是偏重L1的,所以把它设计成2路组相联,而容量却加大到128K,这样既保证了低延迟,又提高的命中率,使的CPU所需要的数据90%以上都可以在L1中找到,而把L2设计成一种辅助缓存,即辅助L1,帮助L1顺利的运行,它保存的数据始终与L1不同,它保存的是L1中替换下来的数据,并设计成16路组相联,来确保L1替换下来的数据都能保存起来,并在CPU呈现时间局部性的时候能够顺利的送回L1。看到这里,读者应该清楚同频下128K L2的闪龙与256K L2的闪龙性能差别不大的原因了吧。

你可能感兴趣的:(散珠碎玉)