程序局部性原理
程序访问的局部性原理包括时间局部性和空间局部性。
时间局部性:在最近的未来要用到的信息,很可能是现在正在使用的信息。
——程序中存在大量循环。
空间局部性:在最近的未来要用到的信息,很可能与现在正在使用的信息存储在邻近空间。
——指令通常是顺序存放、顺序执行的,数据一般也是以向量、数组等形式簇聚在一起存储。
高速缓存技术就是利用程序的局部性访问原理,大大提高了程序的运行速度。
程序局部性原理的应用
假定数组元素按行优先方式存储,对于上面A,B两个程序:
(1)对于数组a的访问,哪个空间局部性更好?哪个时间局部性更好?
(2)对于指令的访问来说,for循环的空间局部性和时间局部性?
假定M、N都为2048,按字节编址,每个数组元素占4B,则指令和数据在主存的存放情况如上图右部分。
(1)对于数组a,程序A和程序B的空间局部性相差较大。
程序A对数组a的访问顺序为a[0][0],a[0][1],.......,a[0][2047];a[1][0],a[1][1],...,a[1][2047];....。由此可见,访问顺序与存放顺序是一致的,因此空间局部性好。
程序B对数组a的访问顺序为a[0][0],a[1][0],...,a[2047][0];a[0][1],a[1][1],....,a[2047][1];...。由此可见,访问顺序与存放顺序不一致,每次访问都要跳过2048个数组元素,即2048*4B=8192B,若主存与Cache的交换单位小于8KB,则每访问一个数组元素都需要装入一个主存块到Cache中,因而没有空间局部性。
两个程序的时间局部性都差,因为每个数组都只被访问一次!!!
(2)对于for循环体,程序A和程序B中的访问局部性是一样的。因为循环体内指令按序连续存放,所以空间局部性好;内循环体被连续重复执行2048*2048次,因此时间局部性也好。
由上述分析可知,虽然程序A和程序B的功能相同,但因内、外两重循环的顺序不同而导致两者对数组a访问的空间局部性相差较大,从而带来执行时间的不同。
for(i=0;i<=9;i++){ temp=1; for(j=0;j<=i;j++)temp*=a[j]; sum+=temp;}//访问顺序a[0],a[0],a[1],a[0],a[1],a[2]...所以既有空间局部性又有时间局部性。
注:指令Cache和数据Cache是分离的!这种结构也称哈佛Cache,特点是允许CPU在同一个Cache存储周期内同时提取指令和数据,由于指令执行过程取指令和取数据都有可能访问Cache,因此这一特性可以保证不同的指令同时访存。
主要是为了避免资源冲突,在五级指令流水线中,分为IF(取址),ID(译码),EXE(执行),MEM(访存),WB(写回)。当然也不一定是五级,现在处理器流水线的长度都在15级左右。但是IF和MEM这两步总是有的。其中IF和MEM都会访问cache。但是IF访问cache是取指令,MEM访问内存是取数据。当前指令的MEM和后面指令IF同时在流水线上执行,会产生同时访问cache的冲突(资源冲突),但是将指令cache和数据cache分开就能满足两者的同时访问了。就不会因为冲突,造成流水线暂停了,提高了流水线运行效率。
Cache命中率
Cache命中率和Cache行大小的关系
行的长度较大,可以充分利用程序访问的空间局部性,使一个较大的局部空间被一起调到Cache中,因而可以增加命中机会。但是,行长也不能太大,主要原因有:
1)行长大使得失效损失变大。也就是说,若未命中,则需要花更多时间从主存读块。
2)行长太大,Cache项数变少,因而命中的可能性变小。
如何提高Cache命中率
1.尽量绑定线程到一个CPU上
这样就避免了数据缓存在多个cpu cache中。
2.频繁更新和不频繁更新的数据分开存放,读数据和写数据分开
实际操作具有一定的难度。
3.数据的分布尽可能的按照访问顺序定义
实际操作具有一定的难度。
4.减少使用全局变量,增强数据访问的局部性
Cache和主存的映射方式
Cache行结构
每个Cache行由标记项和数据项构成。其中数据项就是内存块的副本。大小和内存块相等。
Cache标记项包括:有效位、标记位Tag、一致性维护位、替换算法控制位。
注意:在组相联中,将每组的标记项排成一行,将各组从上到下排成一个二维的标记项阵列(直接映射一行就是一组)。查找Cache时就是查找标记阵列的标记项是否符合要求。例如,下图为二路组相联的标记阵列。
注:标记项阵列(组相联)或标记项组(直接映射)也叫地址映射表。
在考题中一般标记项只计算1位有效位和标记位(即一般不考虑一致维护和替换算法位),其中标记位是可变的,需要通过计算得到,所以是考查重点。
注:
①全相联映射方式:标记位 = 主存块号
②直接映射方式的:“标记位 + Cache行号 = 主存块号”
③组相联映射方式:“标记位 + 组号 = 主存块号”
三种映射方式特点比较
映射方式 | 优点 | 缺点 |
---|---|---|
全相联映射 | Cache存储空间利用充分,命中率高 | 查找标记速度最慢,有可能对比所有行的标记,且标记占的额外空间最多 |
直接映射 | 对于任意一个地址,只需要对比一次标记位,速度最快,且标记所占的额外空间开销最少 | Cache存储空间利用不充分,命中率低 |
组相联映射 | 两种方式的折中,综合效果好 | —— |
一、全相联映射方式——没有限制(全部联合)
1.原理
主存中的每一块可以装入到Cache中的任意位置。
2.访存地址结构
主存块大小 = 行大小 = 64B
3.CPU访问过程
①用主存地址的前22位主存块号与Cache中所有块的标记位对比。
②若匹配,且有效位=1,则Cache命中,再根据块内地址访问对应块中的相应存储单元。
③若未命中或有效位=0,则正常访问主存。
二、直接映射方式——都有限制(全部斗争)
1.原理
主存中的每一块只能装入Cache中的特定位置。若这个位置已有内容,则产生块冲突,原来的块将无条件被替换出去(无须使用替换算法)。
直接映射实现简单,但不够灵活,即使Cache的其他许多地址空着也不能占用,块冲突概率最高,空间利用率最低。
映射关系:Cache行号 = 主存块号%Cache总块数
注:当Cache总块数是2^k时,主存块号的末尾k位即为Cache行号!!
主存块号可以被分为标记部分和行号部分
2.访存地址结构
主存块大小 = 行大小 = 64B
3.CPU访存过程
①根据主存块号的后3位确定Cache行。
②对比主存块号的前19位和Cache的标记位,若匹配,且有效位=1,则Cache命中,再根据块内地址访问对应块中的相应存储单元。
③若未命中或有效位=0,则正常访问主存。
三、组相联映射方式——外部限制,内部无限制(既联合又斗争)
组相联路数的选择:
路数越大,每组Cache行的数量越多,发生冲突的概率越低,但相联比较电路也越复杂(比较标记次数越多)。选择适当的数量,达到折中效果。
1.原理
将Cache空间分成大小相同的组,主存的一个数据块可以装入一组内的任何一个位置,即组间采取直接映射,组内采取全相联映射。
映射关系:所属分组号 = 主存块号%分组数
同理,如果分组数=2^k,则主存块号的末尾k位为所属分组号!
2.访存地址结构
主存块大小 = 行大小 = 64B
3.CPU访问过程
①根据主存块号的后2位确定所属分组号。
②对比主存块号的前20位和分组内的每个Cache行的标记位,若匹配,且有效位=1,则Cache命中,再根据块内地址访问对应块中的相应存储单元。
(注意每行的标记位都是不一样的,不存在每组共有标记位的说法,标记位是指出存的是哪个块的副本)
③若未命中或有效位=0,则正常访问主存。
Cache替换算法
注:对于直接映射方式,由于每个内存块只能对应调入到Cache中的固定位置,所以就不存在选则Cache块,也不会有替换算法。
对于全相联映射:Cache全满了才替换
对于组相联映射:一组满了就要替换!!
1、随机算法(RAND)
随机确定替换的Cache块。实现简单,但未依据程序访问的局部性原理,命中率较低。
2、先进先出算法(FIFO)
选择最早调入的行进行替换。实现简单,但也未依据程序访问的局部性原理。最早进入的主存块也可能是目前经常使用的。
3、最近最久未用算法(LRU)——考查重点
LRU算法对每个Cache行设置一个计数器(LRU位),用计数值来记录主存块的使用情况,根据计数值选择淘汰某个块。
注:LRU位的位数与Cache组大小有关!!n路组相联有log2n位LRU位!!
计数器的变化规则:
①命中时,所命中的行的计数器清零,比其低的计数器加1,其余不变。
②未命中且还有空闲行时,新装入的行的计数器置为0,其余全加1。
③未命中且无空闲行时,计数值最大的行的信息块被淘汰,新装行的计数器置为0,其余全加1。
注意:为什么不是所有计数器都加1,首先,计数器值只是为了比较大小,其余全加1就不能保证计数器值与Cache组大小相匹配,也不会有LRU位的位数=log2n。
4、最不经常使用算法(LFU)
同样为每个Cache块设置一个“计数器”,不过是记录每个Cache块被访问过几次。
LFU的计数器值会很大,曾经被经常访问的主存块在未来不一定会用到,并没有遵循局部性原理,Cache命中率也不高。
Cache写回策略
由于Cache中的内容是主存块副本,当对Cache中的内容进行更新时,就要选择写操作策略使Cache内容和主存内容保持一致。由于只有写操作才会导致Cache和主存中的内容不一致的问题,因此只讨论写操作,此时分两种情况。
Cache写命中——CPU要写的存储单元(所在的块)正好在Cache中有副本
1、写回法(回写法,回写方式,write-back)
当CPU对Cache写命中时,只修改Cache中的内容,而不立即写入主存,只有当这个Cache块被换出时才写回主存。
这种方法减少了访存次数,但存在不一致的隐患(主存中的数据和Cache中的数据不一致)。采用这种策略时,每个Cache行必须设置一个标志位(脏位),以反映此块是否被CPU修改过。——1表示修改过
2、全写法(写直通法,直写方式,write-through)
当CPU对Cache写命中时,必须把数据同时写入Cache和主存。当某一块需要替换时,不必把这一块写回主存,用新调入的块直接覆盖即可。
这种方法实现简单,能随时保持主存数据的正确性。缺点是增加了CPU访存次数,降低了Cache的效率。
为减少全写法直接写入主存的时间损耗,在Cache和主存之间加一个写缓冲(Write Buffer),CPU将数据同时写入Cache和写缓冲而不是Cache和主存,再由写缓冲将内容写回主存,CPU可以做其他事情。
写缓冲是一个FIFP队列,写缓冲可以解决速度不匹配问题。但若出现频繁写时,会使写缓冲饱和溢出。
Cache写不命中——CPU要写的存储单元在只在主存中,不在Cache中
1、写分配法(write-allocate)
把主存中的块加载到Cache中,然后更新这个Cache块。
注:主存中的内容不动,修改的是调入到Cache中的副本,因此该方法通常搭配写回法使用。
此法试图利用程序的空间局部性,但缺点是每次不命中都需要从主存中读取一块。
2、非写分配法(not-write-allocate)
只写入主存,不进行调块。一般搭配全写法
注:也就是说,采用这种方法,只有在读操作未命中时才调入Cache!!
现代计算机的Cache通常设立多级Cache(一般为3级)
各级Cache之间常采用“全写法+非写分配法”
Cache与主存之间常采用“写回法+写分配法”