第7章:存储系统
7.1 存储系统的基本知识
7.1.1 存储系统的层次结构
满足对三个指标的要求:
- 容量大
- 速度快
- 价格低
采用多级存储技术,构成多级存储结构层次
理论依据----程序的局部性原理
对于绝大多数程序来说,程序所访问的指令和数据在地址上不是均匀分布的,而是相对簇聚的。
- 时间局部性:程序马上将要用到的信息很可能就是现在正在使用的信息。
- 空间局部性:程序马上将要用到的信息很可能与现在正在使用的信息在存储空间上是相邻的。
存储系统的多级层次结构
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623140211.png)
假设第 $\mathrm{i} $ 个存储器 $ \mathrm{M}{\mathrm{i}}$ 的访问时间为 $\mathrm{T}{\mathrm{i}} $, 容量为
S
i
S_{\mathrm{i}}
Si , 平均每位价格为 $\mathrm{C}{\mathrm{i}} $, 则
访问时间: $\mathrm{T}{1}<\mathrm{T}{2}<\ldots<\mathrm{T}{\mathrm{n}} $
容量: $ \quad \mathrm{S}{1}<\mathrm{S}{2}<\ldots<\mathrm{S}{\mathrm{n}} $
平均每位价格: $ \mathrm{C}{1}>\mathrm{C}{2}>\ldots>\mathrm{C}{\mathrm{n}} $
整个存储系统要达到的目标:从CPU来看, 该存 储系统的速度接近于 $ M_{1} $ 的, 而容量和每位价格都 拉近于 $ M_{n} $的。
存储器越靠近CPU, 则 CPU对它的访问频度越高, 而且最好大多数的访问都能在
M
1
\mathbf{M}_{1}
M1 完成。
根据局部性原理,我们只要 把近期内CPU访问的程序和数据放在M1中 ,就能使CPU对存储系统的绝大多数访问都能在M1中命中。
存储器之间满足包容关系:
- 任何一层存储器中的内容都是其下一层存储器中的内容的子集。
- CPU在访存时,首先访问 M 1 M_1 M1,如果在M1中找不到所要的数据,就访问 M 2 M_2 M2,将包含所需数据的块或页调入 M 1 M_1 M1,如果在 M 2 M_2 M2中还找不到,就要访问 M 3 M_3 M3,以此类推。
7.1.2 存储层次的性能参数
假设仅仅有两级存储层次:
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623141054.png)
- 存储容量S
- 一般来说,整个存储系统的容量即是第二级存储器 M 2 M_2 M2的容量,即 S = S 2 S=S_2 S=S2
- 平均每位价格C
- C = C 1 S 1 + C 2 S 2 S 1 + S 2 C=\frac{C_{1} S_{1}+C_{2} S_{2}}{S_{1}+S_{2}} C=S1+S2C1S1+C2S2
- 当 S 1 < < S 2 时 , C ≈ C 2 当 S_{1}<<S_{2} 时, C \approx C_{2} 当S1<<S2时,C≈C2
- 命中率
- CPU访问存储系统时,在 M 1 M_1 M1中找到所需信息的概率
-
H
=
N
1
N
1
+
N
2
H = \frac{N_1}{N_1 + N_2}
H=N1+N2N1
- N 1 N_1 N1为访问 M 1 M_1 M1的次数
- N 2 N_2 N2为访问 M 2 M_2 M2的次数
- 不命中率 : F = 1 − H F = 1 - H F=1−H
- 平均访问时间
T
A
T_A
TA
- 分两种情况来考虑CPU的一次访存
- 当命中时,访问时间即为 T 1 T_1 T1(命中时间)
- 当不命中时,访问时间为:
- T 2 + T B + T 1 = T 1 + T M T_2+T_B+T_1=T_1+T_M T2+TB+T1=T1+TM
- T M = T 2 + T B T_M = T_2+T_B TM=T2+TB
- 其中: T M T_M TM为不命中开销:也就是从 M 2 M_2 M2发送请求到把整个数据块调入 M 1 M_1 M1中所需要的时间
- 传送一个信息块所需要的时间为 T B T_B TB
- 所以存储系统的平均访问时间为:
- T A = H T 1 + ( 1 − H ) ( T 1 + T M ) = T 1 + ( 1 − H ) T M 或 T A = T 1 + F T M \begin{array}{c}\mathrm{T}_{\mathrm{A}}=\mathrm{HT}_{1}+(1-\mathrm{H})\left(\mathrm{T}_{1}+\mathrm{T}_{\mathrm{M}}\right) \\=\mathrm{T}_{1}+(1-\mathrm{H}) \mathrm{T}_{\mathrm{M}} \\\text { 或 } \mathrm{T}_{\mathrm{A}}=\mathrm{T}_{1}+\mathrm{FT}_{\mathrm{M}}\end{array} TA=HT1+(1−H)(T1+TM)=T1+(1−H)TM 或 TA=T1+FTM
7.1.3 三级存储系统 🌟🌟
三级存储系统分别为:
- Cache(高速缓冲存储器)
- 主存储器
- 磁盘存储器(辅存)
可以看成是由“Cache—主存”层次和“主存—辅存”层次构成的系统
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623143348.png)
从主存的角度来看:
- cache-主存:弥补主存速度的不足
- 主存-辅存:弥补主存容量的不足
cache-主存层次 🌟
- 主存与CPU的速度差距
-
- 借助于硬件,Cache与主存构成一个有机的整体,以弥补主存速度的不足。
- 这个层次的工作由 硬件 实现。
-
主存和辅存层次 🌟
- 目的:为了弥补主存容量的不足
- 在主存外面增设一个容量更大每位价格更低、速度更慢的存储器---->辅存,一般是硬盘
-
“Cache-主存”与“主存-辅存”层次的区别 🌟🌟
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623144733.png)
7.1.4 存储层次的四个问题
- 当把一个块调入高一层(靠近CPU)存储器时,可以放在哪些位置上?
- (映象规则)
- 当所要访问的块在高一层存储器中时,如何找到该块?
- (查找算法)
- 当发生不命中时,应替换哪一块?
- (替换算法)
- 当进行写访问时,应进行哪些操作?
- (写策略)
下面将逐渐展开问题的解决办法…
7.2 Cache基本知识
7.2.1 基本结构和原理
- Cache和主存分块
- Cache是按块进行管理的。Cache和主存均被分割成大小相同的块。信息以块为单位调入Cache。
- 主存块地址(块号) 用于查找该块在主存中的地址,当将主存地址进行变换后,可以用于查找该块在cache中的位置。
- 块内位移用于确定所访问的数据在该块中的位置。
-
- Cache是按块进行管理的。Cache和主存均被分割成大小相同的块。信息以块为单位调入Cache。
- Cache的基本工作原理示意图
-
7.2.2 映像规则 🌟🌟
---->块从主存调入cache的位置选择
这个和在计算机组成原理中学习的知识是一样的!
全相联映射
- 全相联:主存中的任一块可以被放置到Cache中的任意一个位置。
- 对比:阅览室位置── 随便坐
- 特点:空间利用率最高,冲突概率最低,实现最复杂。
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623151247.png)
也就是说,主存当中的块可以对应cache中的任意一个cache块
直接映射
- 直接映象:主存中的每一块只能被放置到Cache中唯一的一个位置。
- 对比:阅览室位置── 一个位置固定学号尾数为特定数值的学生才能坐。
- 特点:空间利用率最低,冲突概率最高,实现最简单。
- 对于主存的第i块,若它映象到Cache的第j块,则:
- j = i m o d ( M ) ( M 为 C a c h e 的 块 数 ) j=i\mod(M)(M为Cache的块数) j=imod(M)(M为Cache的块数)
比如:设
M
=
2
m
M=2^m
M=2m,则当表示为二进制数时,j实际上就是i的低m位:
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623151655.png)
j = i m o d ( M ) ( M 为 C a c h e 的 块 数 ) j=i \mod (M)(M为Cache的块数) j=imod(M)(M为Cache的块数)
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623151752.png)
当cache块数为8时,也就是 2 3 2^3 23,即主存块号变为二进制后的后三位组成的十进制数,对应于cache中的块号
比如:主存块号为9:(1001),后三位为:001—> 十进制为1,也就是映射到cache中块号为1
组相联映射
-
组相联:主存中的每一块可以被放置到Cache中唯一的一个组中的任何一个位置。
-
对比:阅览室位置── 一个区域只允许一个专业的学生(随意)坐。
-
组相联是直接映象和全相联的一种折衷
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623162125.png)
只不过将上面那个直接相连的M换为组数而已,然后,凡是可以落到这个组里的块在组内是可以进行任意放置的
-
组的选择常采用位选择算法
- 若主存第i块映象到第k组,则: k = i m o d ( G ) k=i \mod(G) k=imod(G)(G为Cache的组数)
- 设 G = 2 g G=2^g G=2g,则当表示为二进制数时, k k k 实际上就是 i i i 的低 g g g 位:
-
- 低g位以及直接映象中的低m位通常称为索引。
-
n路组相联:每组中有n个块(n=M/G ),称该映像规则为n路组相联。
- n 称为相联度
- 相联度越高,Cache空间的利用率就越高,块冲突概率就越低,不命中率也就越低。
- 绝大多数计算机的Cache:n ≤4
7.2.3 查找算法
通过查找目录表来实现
- 目录表的结构
- 主存块的块地址的高位部分,称为标识(tag)
- 每个主存块能唯一地由其标识来确定
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623162909.png)
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623164336.png)
- 只需查找候选位置所对应的目录表项
7.2.5 替换算法 🌟🌟
-
当新调入一块,而Cache又已被占满时,替换哪一块?
- 直接映象Cache中的替换很简单
- 因为只有一个块,别无选择。
- 在组相联和全相联Cache中,则有多个块供选择
- 随机法 🌟
- 优点:实现简单
- 先进先出法FIFO 🌟
- 类似于队列
- 最近最少使用法LRU(Least Recently Used) 🌟
- 选择近期最少被访问的块作为被替换的块。
- 实际上:选择最久没有被访问过的块作为被替换的块。.
- LRU和随机法分别因其不命中率低和实现简单而被广泛采用
- 随机法 🌟
- 直接映象Cache中的替换很简单
-
LRU算法的硬件实现
- 用一个堆栈来记录组相联Cache的同一组中各块被访问的先后次序。
- 用堆栈元素的物理位置来反映先后次序
- 栈底记录的是该组中最早被访问过的块,次栈底记录的是该组中第二个被访问过的块,…,栈顶记录的是刚访问过的块。
- 当需要替换时,从栈底得到应该被替换的块(块地址)
-
- 堆栈中的内容必须动态更新
- 当Cache访问命中时,通过用块地址进行相联查找,在堆栈中找到相应的元素,然后把该元素的上面的所有元素下压一个位置,同时把本次访问的块地址抽出来,从最上面压入栈顶。而该元素下面的所有元素则保持不动。
- 如果Cache访问不命中,则把本次访问的块地址从最上面压入栈顶,堆栈中所有原来的元素都下移一个位置。如果Cache中该组已经没有空闲块,就要替换一个块。这时从栈底被挤出去的块地址就是需要被替换的块的块地址。
- 堆栈法所需要的硬件
- 需要为每一组都设置一个项数与相联度相同的小堆栈,每一项的位数为log2n位。
- 硬件堆栈所需的功能
- 相联比较
- 能全部下移、部分下移和从中间取出一项的功能
- 速度较低,成本较高(只适用于相联度较小的LRU算法)
7.2.6 写策略
“写”操作必须在确认是命中后才可进行
Cache与主存内容的一致性:
- Cache中的内容是主存部分内容的一个副本,
- “写”访问有可能导致Cache和主存内容的不一致
两种写策略 🌟🌟
写策略是区分不同Cache设计方案的一个重要标志。
- 写直达法(也称为存直达法)
- 执行“写”操作时,不仅写入Cache,而且也写入下一级存储器。
- 写直达法的优点:易于实现,一致性好。
- 采用写直达法时,若在进行“写”操作的过程中CPU必须等待,直到“写”操作结束,则称CPU写停顿。
- 减少写停顿的一种常用的优化技术:采用写缓冲器
- 按写分配(写时取)
- 写不命中时,先把所写单元所在的块调入Cache,再进行写入。
- 写回法(也称为拷回法)
- 执行“写”操作时,只写入Cache。
- 仅当Cache中相应的块被替换时,才写回主存。
- (设置“修改位”)
- 写回法的优点:速度快,所使用的存储器带宽较低。
- 不按写分配(绕写法):
- 写不命中时,直接写入下一级存储器而不调块
7.2.7 Cache的性能分析
- 不命中率
- 与硬件速度无关
- 容易产生一些误导
- 平均访存时间 🌟🌟
- 平 均 访 存 时 间 = 命 中 时 间 + 不 命 中 率 × 不 命 中 开 销 平均访存时间=命中时间+不命中率×不命中开销 平均访存时间=命中时间+不命中率×不命中开销
- 程序执行时间
- CPU时间=(CPU执行周期数+存储器停顿周期数)×时钟周期时间
- 存储器停顿时钟周期数=“读”的次数×读不命中率×读不命中开销+“写”的次数×写不命中率×写不命中开销
- 存储器停顿时钟周期数=访存次数×不命中率×不命中开销
- CPU时间=(CPU执行周期数+存储器停顿周期数)×时钟周期时间
7.2.8 改进Cache的性能
平均访存时间=命中时间+不命中率×不命中开销
- 降低不命中率(8种)
- 减少不命中开销(5种)
- 减少Cache命中时间(4种)
7.3 降低Cache不命中率
三种类型的不命中
- 强制性不命中(Compulsory miss)
- 当第一次访问一个块时,该块不在Cache中,需从下一级存储器中调入Cache,这就是强制性不命中。(冷启动不命中,首次访问不命中)
- 容量不命中(Capacity miss )
- 如果程序执行时所需的块不能全部调入Cache中,则当某些块被替换后,若又重新被访问,就会发生不命中。这种不命中称为容量不命中。
- 冲突不命中(Conflict miss)
- 在组相联或直接映象Cache中,若太多的块映象到同一组(块)中,则会出现该组中某个块被别的块替换(即使别的组或块有空闲位置),然后又被重新访问的情况。这就是发生了冲突不命中。
- (也被称为碰撞不命中,干扰不命中)
- 相联度越高,冲突不命中就越少;
- 强制性不命中和容量不命中不受相联度的影响;
- 强制性不命中不受Cache容量的影响,但容量不命中却随着容量的增加而减少。
减少三种不命中的方法:
- 强制不命中: 增加块大小,预取
- 本身很少
- 容量不命中:增加容量
- 抖动现象
- 冲突不命中:提高相联度
- 全相联映射是最理想的情况
许多降低不命中率的方法会增加命中时间或不命中开销
7.3.1 增加Cache块的大小
当Cache的容量不变时,增加Cache块的大小,不命中率与块大小的关系
- 对于给定的Cache容量,当块大小增加时,不命中率开始是下降,后来反而上升了。
- 一方面它减少了强制性不命中;()
- 另一方面,由于增加块大小会减少Cache中块的数目,所以有可能会增加冲突不命中。
Cache容量越大,使不命中率达到最低的块大小就越大。
增加块大小会增加不命中开销
7.3.2 增加Cache的容量
最直接的方法是增加Cache的容量
缺点:
- 增加成本
- 可能增加命中时间
7.3.3 提高相联度
采用相联度超过8的方案的实际意义不大。
2:1 Cache经验规则
容量为N的直接映象Cache的不命中率和容量为N/2的两路组相联Cache的不命中率差不多相同。
7.3.4 伪相联Cache(列相联Cache)
多路组相联的低不命中率和直接映像的命中速度关系
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623175851.png)
基本思想及工作原理
- 在逻辑上把直接映象Cache的空间上下平分为两个区。
- 对于任何一次访问,伪相联Cache先按直接映象Cache的方式去处理。
- 若命中,则其访问过程与直接映象Cache的情况一样。
- 若不命中,则再到另一区相应的位置去查找。
- 若找到,则发生了伪命中,
- 否则就只好访问下一级存储器。
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623180000.png)
快速命中与慢速命中
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623180037.png)
7.3.5 硬件预取
指令和数据都可以预取,预取内容既可放入Cache,也可放在外缓冲器中。
指令预取通常由Cache之外的硬件完成。
预取应利用存储器的空闲带宽,不能影响对正常不命中的处理,否则可能会降低性能。
7.3.6 编译器控制预取
在编译时加入预取指令,在数据被用到之前发出预取请求。
按照预取数据所放的位置,可把预取分为两种类型:
- 寄存器预取:把数据取到寄存器中。
- Cache预取:只将数据取到Cache中
按照预取的处理方式不同,可把预取分为:
- 故障性预取:在预取时,若出现虚地址故障或违反保护权限,就会发生异常。
- 非故障性预取:在遇到这种情况时则不会发生异常,因为这时它会放弃预取,转变为空操作。
在预取数据的同时,处理器应能继续执行。
- 只有这样,预取才有意义。
- 非阻塞Cache (非锁定Cache) :Cache在等待预取数据返回的同时,还能继续为CPU服务。
编译器控制预取的目的:使执行指令和读取数据能重叠执行。
7.3.7 编译优化
- 基本思想:通过对软件进行优化来降低不命中率。
- (特色:无需对硬件做任何改动)
程序代码和数据重组
-
可以重新组织程序而不影响程序的正确性
- 把一个程序中的过程重新排序,就可能会减少冲突不命中,从而降低指令不命中率。
- 把基本块对齐,使得程序的入口点与Cache块的起始位置对齐,就可以减少顺序代码执行时所发生的Cache(强制)不命中的可能性。
-
如果编译器知道一个分支指令很可能会成功转移,那么它就可以通过以下两步来改善空间局部性
- 将转移目标处的基本块和紧跟着该分支指令后的基本块进行对调
- 把该分支指令换为操作语义相反的分支指令
-
相比代码,数据对存储位置的限制更少,更便于调整顺序(用顺序数组代替随机链表)
编译优化技术
- 数组合并
- 将本来相互独立的多个数组合并成为一个复合数组,以提高访问它们的局部性。
- 内外循环变换
- 循环融合
- 将若干个独立的循环融合为单个的循环。这些循环访问同样的数组,对相同的数据作不同的运算。这样能使得读入Cache的数据在被替换出去之前,能得到反复的使用。
- 分块---->大量数据打散
7.3.9 牺牲Cache
牺牲Cache -----> 一种能减少冲突不命中次数而又不影响时钟频率的方法。
- 基本思想:
- 在Cache和它从下一级存储器调数据的通路之间设置一个全相联的小Cache,称为“牺牲”Cache(Victim Cache)。用于存放被替换出去的块(称为牺牲者),以备重用。
- 作用
- 对于减小冲突不命中很有效,特别是对于小容量的直接映象数据Cache,作用尤其明显。
- 例如:项数为4的Victim Cache:能使4KBCache的冲突不命中减少20%~90%
7.4 减少Cache不命中开销
7.4.1 采用两级Cache
-
第一级Cache(L1)小而快
-
第二级Cache(L2)容量大
局部不命中率与全局不命中率 🌟🌟
-
局部不命中率=该级Cache的不命中次数/到达该级Cache的访问次数
-
全局不命中率=该级Cache的不命中次数/CPU发出的访存的总次数
全 局 不 命 中 率 L 2 = 不 命 中 率 L 1 × 不 命 中 率 L 2 全局不命中率_{L2}=不命中率_{L1}×不命中率_{L2} 全局不命中率L2=不命中率L1×不命中率L2
评价第二级Cache时,应使用全局不命中率这个指标。它指出了在CPU发出的访存中,究竟有多大比例是穿过各级Cache,最终到达存储器的。
采用两级Cache时,每条指令的平均访存停顿时间:
每 条 指 令 的 平 均 访 存 停 顿 时 间 = 每 条 指 令 的 平 均 不 命 中 次 数 L 1 × 命 中 时 间 L 2 + 每 条 指 令 的 平 均 不 命 中 次 数 L 2 × 不 命 中 开 销 L 2 每条指令的平均访存停顿时间\\ = 每条指令的平均不命中次数_{L1}×命中时间_{L2}\\+每条指令的平均不命中次数_{L2}×不命中开销_{L2} 每条指令的平均访存停顿时间=每条指令的平均不命中次数L1×命中时间L2+每条指令的平均不命中次数L2×不命中开销L2
例题:
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623193625.png)
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/C7AE223567EBECE7DF512BB866B12282.png)
停顿时间,也就是除了第一次cache命中的时间,其它的算是停顿时间
访存时间:一次访问数据所需要的时间(数据可能在cache也可能在主存,也可能在硬盘)
每条指令的访存时间:访存时间*每条指令的访存次数
每条指令的停顿时间:每条指令的访存次数*(停顿时间 = 访存时间-在cache中获得数据的时间)
对于第二级Cache,我们有以下结论:
-
在第二级Cache比第一级Cache大得多的情况下,两级Cache的全局不命中率和容量与第二级Cache相同的单级Cache的不命中率非常接近
-
局部不命中率不是衡量第二级Cache的一个好指标,因此,在评价第二级Cache时,应用全局不命中率这个指标。
-
第二级Cache不会影响CPU的时钟频率,因此其设计有更大的考虑空间。
第二级cache的参数
-
容量
- 第二级Cache的容量一般比第一级的大许多。
- 大容量意味着第二级Cache可能实际上没有容量不命中,只剩下一些强制性不命中和冲突不命中
-
相联度
- 第二级Cache可采用较高的相联度或伪相联方法
-
块大小
- 第二级Cache可采用较大的块
- 为减少平均访存时间,可以让容量较小的第一级Cache采用较小的块,而让容量较大的第二级Cache采用较大的块。
- 多级包容性
- 第一级Cache中的数据总是同时存在于第二级Cache中
- 能够很方便实现I/O和Cache之间数据一致性
7.4.2 让读不命中优先于写
- Cache中的写缓冲器导致对存储器访问的复杂化
- 在读不命中时,所读单元的最新值有可能还在写缓冲器中,尚未写入主存
- 解决问题的方法(读不命中的处理)
- 推迟对读不命中的处理(被动等)
- 检查写缓冲器中的内容(主动检查)
7.4.3 写缓冲合并
- 提高写缓冲器的效率
- 写直达Cache
- 依靠写缓冲来减少对下一级存储器写操作的时间。
- 如果写缓冲器为空,就把数据和相应地址写入该缓冲器
- 从CPU的角度来看,该写操作就算是完成了。
- 如果写缓冲器中已经有了待写入的数据,就要把这次的写入地址与写缓冲器中已有的所有地址进行比较,看是否有匹配的项。
- 如果有地址匹配而对应的位置又是空闲的,就把这次要写入的数据与该项合并,这就叫写缓冲合并。
- 如果写缓冲器满且又没有能进行写合并的项,就必须等待。
- 使用写合并能够提高写缓冲器的空间利用率,而且还能减少因写缓冲器满而要进行的等待时间。
7.4.4 请求字处理技术
- 请求字
- 从下一级存储器调入Cache的块中,只有一个字是立即需要的。这个字称为请求字。
- 应尽早把请求字发送给CPU
- 尽早重启动:调块时,从块的起始位置开始读起。一旦请求字到达,就立即发送给CPU,让CPU继续执行。
- 请求字优先:调块时,从请求字所在的位置读起。这样,第一个读出的字便是请求字。将之立即发送给CPU。
- 这种技术在以下情况下效果不大:
- Cache块较小
- 下一条指令正好访问同一Cache块的另一部分
7.4.5 非阻塞Cache技术
非阻塞Cache:Cache不命中时仍允许CPU进行其它的命中访问。即允许“不命中下命中”。
- 不命中重叠:
- 多重不命中下命中
- 不命中下不命中
- 存储器必须能够处理多个不命中
可以同时处理的不命中次数越多,所能带来的性能上的提高就越大。但并非不命中次数越多越好。
不足:非阻塞Cache增加了Cache控制器的复杂度,尤其是多重叠非阻塞Cache
7.5 减少命中时间
命中时间直接影响到处理器的时钟频率。在当今的许多计算机中,往往是Cache的访问时间限制了处理器的时钟频率。
7.5.1 容量小,结构简单的Cache
- 硬件越简单,速度就越快;
- 应使Cache足够小,以便可以与CPU一起放在同一块芯片上。
7.5.2 虚拟Cache
物理Cache VS 虚拟Cache
-
物理Cache
- 使用物理地址进行访问的传统Cache
- 标识存储器中存放的是物理地址,进行地址检测也是用物理地址
-
-
虚拟Cache
- 可以直接用虚拟地址进行访问的Cache。标识存储器中存放的是虚拟地址,进行地址检测用的也是虚拟地址。
-
- 优点:在命中时不需要地址转换,省去了地址转换的时间。即使不命中,地址转换和访问Cache也是并行进行的,其速度比物理Cache快很多。
-
7.5.3 Cache访问流水线
对第一级Cache的访问按照流水的方式进行组织,将Cache的访问分解为多个时钟周期。
目的:提高时钟频率
:Intel的Pentium访问Cache需要1个时钟周期,Pentium Pro和奔三需要2个,奔四则需要4个时钟周期。
单位时间做的事情可以少一点,分成几步去做;步数的增加可以使用流水的方式来抵消。
即并没有直接减少Cache的命中时间,而是提高了单位时间内Cache的输出数据量,及提高了访问Cache的带宽。
7.5.4 跟踪Cache
开发指令级并行性所遇到的一个挑战是:
- 当要每个时钟周期流出超过4条指令时,要提供足够多条彼此互不相关的指令是很困难的
一个解决方法:采用踪迹Cache
指令Cache存放CPU所执行的动态指令序列
- 包含了由分支预测展开的指令,该分支预测是否正确需要在取到该指令时进行确认
7.6 并行主存系统
主存的主要性能指标:延迟和带宽
并行主存系统是在一个访存周期内能并行访问多个存储字的存储器。
一个单体单字宽的存储器
- 字长与CPU的字长相同。
- 每一次只能访问一个存储字。假设该存储器的访问周期是
T
M
T_M
TM,字长为
W
W
W位,则其带宽为:
- B M = W T M B_M = \frac{W}{T_M} BM=TMW
-
在相同的器件条件(即TM相同)下,可以采用两种并行存储器结构来提高主存的带宽:
- 单体多字存储器 (就是一个存储器)
- 多体交叉存储器
7.6.1 单体多字存储器
一个单体m字(这里m=4)存储器
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/20220623182011.png)
-
存储器能够每个存储周期读出m个CPU字。因此其最大带宽提高到原来的m倍。
- B M = m × W T M B_M = m \times \frac{W}{T_M} BM=m×TMW
- 单体多字存储器的实际带宽比最大带宽小
-
优缺点:
- 优点:实现简单
- 缺点:访存效率不高
- 如果一次读取的m个指令字中有分支指令,而且分支成功,那么该分支指令之后的指令是无用的。
- 一次取出的m个数据不一定都是有用的。另一方面,当前执行指令所需要的多个操作数也不一定正好都存放在同一个长存储字中。
- 写入有可能变得复杂。
- 当要读出的数据字和要写入的数据字处于同一个长存储字内时,读和写的操作就无法在同一个存储周期内完成。
7.6.2 多体交叉存储器
多体交叉存储器:由多个单字存储体构成,每个体都有自己的地址寄存器以及地址译码和读/写驱动等电路
编址方法:
- 高位交叉编址(对存储单元矩阵按列优先编址)
- 低位交叉编址(对存储单元矩阵按行优先编址)
在计算机组成原理里面有详细介绍,体系结构并不过多关注于这个
7.6.3 避免存储体冲突
- 体冲突:两个请求要访问同一个存储体。
- 减少体冲突次数的一种方法:采用多体
7.7 虚拟存储器
- 虚拟存储器是“主存—辅存”层次进一步发展的结果
- 虚拟存储器可以分为两类:页式和段式
- 页式虚拟存储器把空间划分为大小相同的块。(页面)
- 段式虚拟存储器则把空间划分为可变长的块。(段)
- 页面是对空间的机械划分,而段则往往是按程序的逻辑意义进行划分。
- Cache和虚拟存储器的参数取值范围
-
7.8 Cache优化技术总结 🌟🌟
总结
- “+”号:表示改进了相应指标。
- “-”号:表示它使该指标变差。
- 空格栏:表示它对该指标无影响。
- 复杂性:0表示最容易,3表示最复杂。
优化技术 | 不命中率 | 不命中开销 | 命中时间 | 硬件复杂程度 | 说明 |
---|---|---|---|---|---|
增加块大小 | + | − | 0 | 实现容易,Pentium 4的第二级Cache采用128字节的块 | |
增加Cache容量 | + | 1 | 被广泛采用,特别是第二级Cache | ||
提高相联度 | + | - | 1 | 被广泛采用 | |
牺牲Cache | + | - | 2 | AMD Athlon采用了8个项的Victim Cache | |
伪相联Cache | + | 2 | MIPS R10000的第二级Cache采用 | ||
硬件预取指令和数据 | + | 2-3 | 许多机器预取指令,UltraSPARC Ⅲ预取数据 | ||
编译器控制的预取 | + | 3 | 需同时采用非阻塞Cache;有几种微处理器提供了这种预取的支持 | ||
用编译技术减少Cache不命中次数 | + | 0 | 向软件提出了新要求;有些机器提供了编译器选项 | ||
使读不命中优先于写 | + | - | 1 | 在单处理机上实现容易,被广泛采用 | |
写缓冲合并 | + | 1 | 与写直达合用,广泛应用,例如21164,UltraSPARCⅢ | ||
尽早重启动和关键字优先 | + | 2 | 被广泛采用 | ||
非阻塞Cache | + | 3 | 在支持乱序执行的CPU中使用 | ||
两级Cache | + | 2 | 硬件代价大;两级Cache的块大小不同时实现困难;被广泛采用 | ||
容量小且结构简单的Cache | - | + | 0 | 容易实现,被广泛采用 | |
对Cache进行索引时不必进行地址变换 | + | 2 | 对于小容量Cache来说实现容易,已被Alpha21164和UltraSPARC Ⅲ采用 | ||
流水化Cache访问 | + | 1 | 被 | ||
踪迹Cache | + | 3 | Pentium 4 采用 |
计算题
平 均 访 存 时 间 = 命 中 时 间 + 不 命 中 率 × 不 命 中 开 销 平均访存时间=命中时间+不命中率×不命中开销 平均访存时间=命中时间+不命中率×不命中开销
-
cache\主存系统的平均访问时间
- 若 t c \mathbf{t}_{\mathrm{c}} tc 表示命中时的cache访问时间, t m \mathrm{t}_{\mathrm{m}} tm 表示未命中时的主存访问肘间, 1 − h 1-h 1−h表示未命中率, 则cache/主存系统的平均访问时间 t a \mathbf{t}_{\mathrm{a}} ta :
- t a = h t c + ( 1 − h ) t m t_{a}=h t_{c}+(1-h) t_{m} ta=htc+(1−h)tm
-
访问效率
- 访问效率 e e e:
- e = t c t a e=\frac{t_{c}}{t_{a}} e=tatc
例题:
CPU执行一段程序时, cache完成存取的次数为 1900 次, 主存完成存取的次数为 100 次
已知cache存取周期为 50 n s 50 \ ns 50 ns, 主存存取周期为 250 n s 250 \mathrm{~ns} 250 ns
求cache/主存系统的效率和平均访问时间
![](https://fastly.jsdelivr.net/gh/NEUQer-xing/Markdown_images/images/61EF4A23CAC988B5D306D2917A07BA1C.png)