为什么引入Cache?
因为IO设备向主存请求的级别高于cpu,会出现cpu等待IO访问cpu的情况,进而导致cpu使用效率的降低。解决主存和cpu速度不匹配的问题。
Cache依赖的原理?
时间局部性:近期要访问的数据很可能是现在要访问的数据,因为有循环。
空间局部性:近期要访问数据的所在地址,很可能就是现在访问数据地址的附近。
Cache和主存的映射方式
cache里面的数据实际上就是主存数据的一份复制。那么主存的数据该怎么放进cache中,引出映射的不同方式。而为存入cache里面的块为了与主存的块保持对应,还需要加上标记。还需要添加有效位来保存当前cache块的状态。
标记:用于指出在cache里面的是主存的哪一块的副本。
例如:cache 中有 0 1 2 3 ,此时cpu要访问主存的第4块和第8块数据,主存里第四块数据如果根据组相连映射法,4将映射到cache的第(4mod4=0)组,8将映射到cache的第(8mod4=0)组.这时候如果不加上一个标识,那都分不清楚4和8在cache里第0组里面谁是谁了,因为他们都是0。有效位:为1:该块已经放了。为0:没放。
1、直接映射
主存里面的每一块只可以放在cache里固定的位置,不能放别人那。后来的、产生冲突的块,直接把原来的踢走。
j:Cache块号
i:主存块号
c:Cache总块数
直接映射的关系: j = i mod c
地址结构
标记 | Cache行号 | 块内地址 |
---|
cache命中:
1、首先找到地址结构中的cache行号对应的在cache里内容。
2、根据该cache块的标记位与主存的标记位来进行比较。
3、如果相等并且有效位=1,则说明cache命中。
cache未命中:
1、从cpu内读取所需要数据在主存中的位置。
2、找到以后读出信息,同时送去对应的cache行,还要把信息送至cpu
优点:实现简单。
缺点:空闲块利用率较低,块冲突的概率高。不够灵活。
2、全相联映射
主存的每一块可以放进cache中空闲的任意一个位置。cpu访存时需要和所有的cache行的标记进行比较,因为它的放置与主存的位置之间没有规律可循。
地址结构:
标记 | 块内地址 |
---|
优点:冲突概率低,提高了空闲块的利用率,命中率较高。
缺点:标记的比较速度比较慢,实现成本高,通常要用昂贵的相联存储器来进行地址映射。
3、组相联映射
结合全相联映射和直接映射的方式,将cache进行分组,主存里的每一块固定对应cache的某一组,不能去别的组。而组内是使用了全相联映射的方式,组内随机存放。
二路组相联--------将cache分成2块为一组
(主存地址块号)mod(cache的分组数)= 该块在cache当中的所在组号。
地址结构
标记 | 组号 | 块内地址 |
---|
路数越多则冲突概率越低,但是路数越多也就以为着电路的设计更加的复杂。成本接近直接映射,性能接近全相联映射。
Cache主存块的替换算法
当cache已经存满了的时候,该如何选择被换出的cache块?
1、随机算法
随机从cache里面挑选一名幸运的人被换出去,可惜没依据程序访问的局部性原理,因为随机挑一个有可能是工作人员,他要一直被使唤干活的(即该块使用率较高)。命中率比较低。
2、先进先出算法(堆栈类算法)
从cache里面选最开始就来了的人,谁先到谁先滚蛋。也不是很好,因为如果是工作人员,那他最早来的cache被换走了就很麻烦。因为他比较忙,可能经常是要使唤去干活的。因此也没有依据程序访问的局部性原理
3、最近最少使用算法(堆栈类算法)
选cache里面近期没有被使唤的工作人员换出,稍微合理了些,依据了局部性原理。那么系统是怎么知道cache里面谁被使唤的时间最少呢?
对cache每个工作人员设置一个计数器,每当一个工作人员被使唤一次则计数器归0,比他低的+1。
抖动:集中访问的存储区超过cache组的大小时,命中率可能变得非常低。
4、最不经常使用算法
将一段时间内访问次数最少的块进行换出操作,设置计数器,使用一次便++,最后计数器值最小的就是最不经常使用的。
Cache写策略
在通过cache进行访问数据的时候,难免会有数据出现修改要写回主存的情况,那么对于写回主存又有以下几种策略。
cache命中:
1、全写法:当cpu对cache写命中时,必须把数据同时写入内存
但是由于写回cpu写回主存的速度和写回cache的速度不匹配(写回主存更加慢),所以添加一个写缓冲(FIFO队列),cpu把要修改的数据给了cache和缓冲池以后就去干别的事情,由缓冲自己慢慢写回主存。但是如果频繁写入的话容易造成写缓冲溢出。
优点:随时保持数据的正确性
缺点:增加了访存的次数,降低了cache的效率
2、写回法:cpu命中后修改cache里面的数据,并不会立即写回主存,只有在这一块被换出去的时候才会去看这块是否修改了,才决定要不要写回主存。
那么怎么确定该块是否被修改了?
设置脏位,脏位=1,修改了,反之没有被修改。
cache不命中
1、写分配法
加载主存的块到cache里面,更新这个cache块。缺点是每一次不命中都要去主存里面读取一块。通常配合写回发使用,因为调块去了cache里面就意味着将会被命中,则也是设置脏位
2、非写分配法
只写入主存,不进行调块