本篇不是学习课程时的笔记,是重看这本书时的简记。对于学习本课程的同学,未涉及的内容不代表考试不涉及,部分省略的部分是在该课程的讨论课中学习的(存储器山,矩阵乘法),对于核心内容的掌握,需要学习相关实例和习题(缓存查找)。
计算机系统-存储器层次结构
1.存储技术
本部分主要介绍各种存储技术(SRAM,DRAM,ROM,磁盘及读写,固态硬盘),从这些技术中可总结的重要的思想为:
- 不同的存储技术有不同的价格和性能折中
- 从不同存储器的价格和性能变化速率不同
- 增加密度比降低访问时间更容易
2.局部性
一个编写良好的程序应该具有良好的局部性,有两类局部性:
- 时间局部性:一个被引用的存储器位置很可能很快被再次引用
- 空间局部性:如果一个存储器位置被引用了,则其相邻的位置很可能很快被引用
体现在程序中,一个变量经常被访问,则符合时间局部性;连续的数据以合适的步长访问,符合空间局部性。(例如多维数组按行访问,按序取指令)
3.存储器层次结构
层次结构
存储器层次结构设计的核心思想在于:每层的较快、较小存储作为位于下层的更大、更慢的存储的缓存。
由于局部性,程序更倾向于访问高层次的数据;而下层的存储存取更慢,空间更大,价格便宜。通过这样的存储器层次结构设计,实现了大容量存储+低价格+高速访存。
缓存基本概念
数据在不同的存储层次之间以块的形式传输,按块排列在存储器中。不同层次之间的块大小可能不同。越上层块越小,例如L1和L2之间只有8-16个字。
在访问存储器K+1时,优先访问高层次的存储K+1,如果要找的数据对象在K层存在,就称为缓存命中。以更快的速度读取到了K+1层的数据。如果K层没有要找的数据,则缓存不命中。不命中的类型有三种:
- 冷不命中:最开始时,K层存储器为空,一定会不命中,但是这个不命中只会短暂出现
- 冲突不命中:由于K层存储比K+1层更小,K+1层的多个块映射到K层的同一个位置,这样即使缓存足够大,连续访问映射到一个位置的多个块也会导致不命中
- 容量不命中:访问的块的集合(称之为工作集)大小超出缓存大小,导致不命中
不同的层次结构之间要传输数据,必须要有逻辑对缓存进行管理。对于L1、L2、L3这样的高速缓存,是由硬件逻辑管理的,下层的主存作为磁盘的缓存,是由操作系统软件和地址翻译硬件共同管理的。
4.高速缓存存储器
在考虑存储层次和缓存时,通常从四个问题考虑:
- 缓存组织
- 缓存查找
- 缓存替换
- 缓存读与写入策略
缓存组织
缓存组织的方式有三种:
- 全相联:K+1层的一块可以放到K层的任意一个位置。空间利用率高,冲突概率低,实现复杂
- 直接映射:K+1层的一块只能放到K层的一个指定位置。空间利用率低,冲突概率高,实现简单
- 组相联:K+1层的一块可以放到K层的一些位置(一组当中),是全相联和直接映射的折中
下图中K+1层为主存,K层为Cache:
实际上全相联映射和直接映射可以看作是特殊的组相联映射。
缓存查找
缓存组织的三种方式都最终归结为组相联,由此可以得出缓存的通用结构:
缓存由多个组构成,每一组中有多行,每一行中有数据块和用于找到该块的信息,以及一个标记位表示该缓存数据是否有效。
如果要从缓存查找到一个数据,必须要有组索引,行标记,和数据在块中的偏移。在组内的查找使用逐个查找的方式,所以没有使用行索引,而是使用标记(Tag)来匹配。由此可以将地址划分为:
地址被划分为t、s、b位的三部分,分别用于标记,组索引,偏移。根据这三部分的位数,可以有
2
s
2^s
2s个组,
2
b
2^b
2b个字节数据,最多
2
t
2^t
2t行,但是缓存往往没有这么多行,因此规定每组有E行。据此,缓存的大小为:
C
a
c
h
e
S
i
z
e
=
S
×
E
×
B
(
b
y
t
e
s
)
Cache\ Size = S\times E \times B \ (bytes)
Cache Size=S×E×B (bytes)
而缓存查找的过程,只需要从地址中分离出行标记,组索引,块偏移,然后按照先查找组,再匹配行,最后检查有效位并根据偏移取出数据的顺序工作就可以完成。
|补充:组索引的位置
在上面划分地址时,第一步应该是得到组索引,却没有使用高位作为组索引,而是高位作为标记,中间位作为组索引。这样做的原因是避免冲突不命中。我们假设现在只有四个组,每组有一行,用这个情况说明为什么不用高位做组索引的原理:
假设按顺序循环访问数据块0000、0001、0010、0011,如果用高位作组索引,则这四块会映射到同一组,在循环中会反复发生冲突不命中,如果用低位作组索引,这四块会被映射到四个组中,在循环访问时,只有第一次循环的冷不命中。因此采用变化更快的低位为组索引能够一定程度的避免冲突不命中。由于地址低位在各层次及虚拟存储中都默认为偏移量,采用中间位作为组索引。这个问题的核心是用变化比较快的位作为组索引,可以尽量让临近的数据分布到不同的组,避免冲突不命中。
缓存替换
访问一个块不命中时,要把K+1层的块复制到K层,这样下次就可以更快的存取该块。如果K层缓存已满,就要替换掉一块数据,常用的策略有:
- 随机替换
- FIFO
- LRU
- LFU
实际常用的策略是LRU和LFU,替换后的命中率较高。不过需要记录访问块最近是否访问过。
|补充:抖动现象
对于直接映射缓存,常出现冲突不命中,称为抖动现象。例如假设A[0]和A[1]都映射到同一个位置,循环访问A[0]和A[1],每一次都是不命中的,这种现象称为抖动。
FIFO策略也是导致这种现象的原因之一,假设有缓存有10个数据块大小,而循环访问11个数据块,FIFO策略会导致每一次都是不命中,在这10个块之间抖动。
缓存读写策略
对于读缓存,策略很简单,如果K层没有需要的数据,就从K+1层读取数据到K层(可能需要替换出一些块)。写策略要复杂一些,因为K和K+1层的块都要更新。写有两种策略:
- 直写:将数据写入K和K+1
- 写回:将数据写入K,当K中该块要被替换了,才写入K+1
写回能减少数据流量,但是缓存必须维护块是否被修改过的信息(添加修改位)。通常低层次的缓存都采用写回,因为低层次之间的数据传输更慢。
如果写入时发生了不命中,则有两种处理策略:
- 写分配:将K+1层的数据读到K层,写入
- 非写分配:直接写入K+1层
一般来说,直写高速缓存是非写分配的,写回高速缓存是写分配的。
缓存的性能评价与参数影响
上面已经讨论了四个高速缓存的问题,接下来分析高速缓存的性能评价和参数影响。
由于不命中对性能的影响很大,性能评价指标以不命中情况为主:
- 不命中率:不命中数量/引用数量
- 不命中处罚:不命中导致从K+1层取数据的时间
影响高速缓存性能主要是以下几个方面:
- 高速缓存大小:大的缓存命中率高,但速度可能会慢
- 块大小:大块空间局部性要好,但对于给定缓存大小,大块会使行数减少,增加冲突不命中,影响时间局部性,大块的不命中处罚也更大
- 相联度:即组中的行数E,E较大则复杂性更高,匹配时间更长,但可以减少抖动
- 写策略
5.高速缓存对性能的影响
本部分主要了解存储器山的概念,矩阵乘法。