文章目录
第7章 存储系统
7.2 Cache基本知识
7.2.1 基本结构和原理
Cache和主存分块
- Cache是按块进行管理的。Cache和主存均被分割成大小相同的块。信息以块为单位调入Cache。
- 主存块地址(块号)用于查找该块在Cache中的位置。
- 块内位移用于确定所访问的数据在该块中的位置。
Cache的基本工作原理
示意图
当CPU要访问存储器系统时,它把地址送入主存地址寄存器。“主存→Cache地址转换” 部件判定包含所访问数据或指令的块是否已在Cache中。
-
如果在Cache中(即
命中
),“主存→Cache地址转换” 部件就把主存地址中的块地址转换成Cache块地址,放入Cache地址寄存器,块内位移保持不变。然后用所形成的地址去访问Cache,把所得的数据或指令送给CPU。 -
如果不在Cache中(即
不命中
),就需要用主存地址去访问主存储器,把相应的块调入Cache,然后再把CPU所要的数据或指令送给CPU。 -
当然,也可以把CPU所要的数据或指令直接送给CPU(如图中的虚线所示),以提高性能。
-
在进行调块前,需先判定Cache 中是否还有位置来存放将调入的块。如果没有,就需要按某种替换策略先从Cache中把某个块替换出去,然后将新调入的块放到被替换的块的位置上。
7.2.2 映象规则
解决将主存块调入Cache时可以放置到哪些位置。映像规则有三种。
全相联映象
- 主存中的任一块可以被放置到Cache中的任意一个位置。
- 优点:空间利用率最高,冲突概率最低
- 缺点:实现最复杂。
全相联映像举例:
直接映象
- 主存中的每一块只能被放置到Cache中唯一的一个位置。
- 优点:实现最简单。
- 缺点:空间利用率最低,冲突概率最高,
说明:对于主存的第i
块,若它映象到Cache的第j
块,则:j=i mod M
(M为Cache的块数)
设
M
=
2
m
M=2^{m}
M=2m,则当表示为二进制数时,j
实际上就是i
的低m位
:
直接映像举例:
组相联映象
组相联是直接映象和全相联的一种折衷
- 主存中的每一块可以被放置到Cache中唯一的一个组中的任何一个位置。
组的选择常采用位选择算法
-
若主存第
i
块映象到第k
组,则:k = i mod G
(G为Cache的组数) -
设 G = 2 g G=2^{g} G=2g,则当表示为二进制数时,
k
实际上就是i
的低g
位:
索引(index)
- 组相联映象中主存块地址
低g位
、直接映象中主存块地址的低m位
通常称为索引。
n路组相联
- 每组中有n个块
(n=M/G)
。 - n 称为
相联度
。 - 相联度越高,Cache空间的利用率就越高,块冲突概率就越低,不命中率也就越低。
三种映像关系与路数、组数的关系
n(路数) | G(组数) | |
---|---|---|
全相联映像 | M M M | 1 1 1 |
直接映像 | 1 1 1 | M M M |
组相联映像 | 1 < n < M 1 < n < M 1<n<M | 1 < G < M 1 < G < M 1<G<M |
Cache的容量与索引index、相联度、块大小之间的关系
- Cache的容量 = 2 i n d e x 2^{index} 2index × 相联度 × 块大小
7.2.3 查找方法
解决以下问题:
- 当CPU访问Cache时,如何确定Cache中是否有所要访问的块?
- 若有的话,如何确定其位置?
1. 通过查找目录表来实现
目录表的结构
- 主存块的块地址的高位部分,称为标识 。每个主存块能唯一地由其标识来确定
- 存放目录表的寄存器又叫做标识寄存器
有效位
- 目录表给每一项设置了一个有效位,用于指出Cache中的块是否包含有效信息。
- 例如:当一个主存块被调入Cache中的某一个块的位置事,它的标识就被填入目录表中与该Cache块相对应的项中,并将其有效位置“1”
候选位置
- 根据映像规则的不同,一个主存块可能映像到Cache中的一个或多个Cache块位置。将这些位置称为候选位置
查找
- CPU访问某一主存块时,只需要查找它的候选位置的目录表项(标识)
- 如果有标识匹配的项,且有效位为“1”,则它对应的Cache块就是要找的块
2. 并行查找的实现方法
在采用直接映像或是组相联映像的情况下,为了提高访问速度,一般是把
“主存→Cache” 地址变换
和访问Cache存储体
安排成同时进行实现并行查找的方法有两种:
- 用相联存储器实现
- 用单体多字的按地址访问的存储器和比较器来实现
相联存储器
- 目录由 2 g 2^{g} 2g个相联存储区构成,每个相联存储区的大小为 n × ( h + log 2 n ) n×(h + \log_{2} n) n×(h+log2n)位。
- 根据所查找到的组内块地址,从Cache存储体中读出的多个信息字中选一个,发送给CPU。
单体多字存储器+比较器
以四路组相联为例,这时需要4个比较器。
查找过程:
-
CPU访存时,用本次访存地址中的索引作为地址从标识存储器中选取一行(对应于一组),并从该行并行读出4 个标识(分别对应于4路)。
-
与此同时,也从Cache存储体中并行读出4个信息字。然后将所读出的4个标识与本次访存地址中的标识进行并行比较,以确定是否命中以及该组中哪一个块是要访问的块(若命中)。
-
根据这些信息,就可以从已经读出的4个信息字中选择一个,送给CPU。
优点
- 不必采用相联存储器,而是用按地址访问的存储器来实现。
- 所需要的硬件为:大小为 2 g × n × h 2^{g} × n × h 2g×n×h位的存储器和 n n n个 h h h位的比较器。
缺点
- 当相联度n增加时,不仅比较器的个数会增加,而且比较器的位数也会增加。
7.2.4 替换算法
所要解决的问题:当新调入一块,而Cache又已被占满时,替换哪一块?
- 直接映象Cache中的替换很简单(因为只有一个块,别无选择。)
- 在组相联和全相联Cache中,则有多个块供选择。
主要的替换算法有三种
-
随机法
- 随机选择被替换的块
- 优点:实现简单,容易用硬件实现
- 缺点:没有考虑Cache块过去被使用的情况,反应不了程序的局部性,所以命中率较低
-
先进先出法FIFO
- 选择最早调入的块作为被替换的块
- 优点:容易实现
- 缺点:不能正确的反映程序的局部性,因为最先进入的块,也可能是要经常使用到的块
-
最近最少使用法LRU
- 选择近期最少被访问的块作为被替换的块
- 优点:比较好的反映了程序的局部性原理,命中率高
- 缺点:硬件实现成本比较高
LRU
和随机法
分别因其不命中率低和实现简单而被广泛采用。
- 模拟数据表明,对于容量很大的Cache,LRU和随机法的命中率差别不大。
LRU算法的硬件实现
- 堆栈法
- 用一个堆栈来记录组相联Cache的同一组中各块被访问的先后次序。
- 用堆栈元素的物理位置来反映先后次序
- 栈底记录的是该组中最早被访问过的块,次栈底记录的是该组中第二个被访问过的块,…,栈顶记录的是刚访问过的块。
- 当需要替换时,从栈底得到应该被替换的块(块地址)。
堆栈内容更新策略:
-
当Cache访问命中时,通过用块地址进行相联查找,在堆栈中找到相应的元素,然后把该元素的上面的所有元素下压一个位置,同时把本次访问的块地址抽出来,从最上面压入栈顶。而该元素下面的所有元素则保持不动。
-
如果Cache访问不命中,则把本次访问的块地址从最上面压入栈顶,堆栈中所有原来的元素都下移一个位置。如果Cache中该组已经没有空闲块,就要替换一个块。这时从栈底被挤出去的块地址就是需要被替换的块的块地址。
堆栈法所需要的硬件
- 需要为每一组都设置一个项数与相联度相同的小堆栈,每一项的位数为 log 2 n \log_2n log2n位。
硬件堆栈所需的功能
- 相联比较
- 能全部下移、部分下移和从中间取出一项的功能
堆栈法总结
速度较低,成本较高
(只适用于相联度较小的LRU算法)
- 比较对法
基本思路
- 让各块两两组合,构成比较对。每一个比较对用一个触发器的状态来表示它所相关的两个块最近一次被访问的远近次序,再经过门电路就可找到LRU块。
举例
有 A 、 B 、 C A、B、C A、B、C三个块,可以组成3对: A B 、 A C 、 B C AB、AC、BC AB、AC、BC.每一对中块的访问次序分别用“对触发器” T A B 、 T A C 、 T B C T_{AB}、T_{AC}、T_{BC} TAB、TAC、TBC表示。
- T A B T_{AB} TAB为“1”,表示A比B更近被访问过;
- T A B T_{AB} TAB为“0”,表示B比A更近被访问过。
- T A C 、 T B C T_{AC}、T_{BC} TAC、TBC也是按这样的规则定义。
显然,当 T A C = 1 T_{AC}=1 TAC=1 且 T B C = 1 T_{BC}=1 TBC=1 时, C C C就是最久没有被访问过了。( A A A比 C C C更近被访问过、且 B B B比 C C C也是更近被访问过)
即: C L R U = T A C ⋅ T B C C_{LRU} = T_{AC}·T_{BC} CLRU=TAC⋅TBC
同理可得:
B
L
R
U
=
T
A
B
⋅
T
‾
B
C
A
A
L
U
=
T
‾
A
B
⋅
T
‾
A
C
B_{LRU} = T_{AB} · \overline{T}_{BC} \quad A_{ALU} = \overline{T}_{AB} · \overline{T}_{AC}
BLRU=TAB⋅TBCAALU=TAB⋅TAC
用触发器和与门实现上述逻辑的电路:
比较对法所需的硬件量
-
与门
- 有多少个块,就要有多少个与门; 每个与门的输入端要连接所有与之相关的触发器。
- 对于一个具有
P
块的组中的任何一个块来说,由于它可以跟除了它自己以外的所有其他的块两两组合,所以与该块相关的比较对触发器个数为P-1
,因而其相应的每个与门的输入端数是P-1
。
-
触发器
- 所需要的触发器的个数与两两组合的比较对的数目相同。
组内块数 | 3 | 4 | 8 | 16 | 64 | 256 | … | P |
---|---|---|---|---|---|---|---|---|
触发器个数 | 3 | 6 | 28 | 120 | 2016 | 32640 | … | P ( P − 1 ) 2 \frac{P(P-1)}{2} 2P(P−1) |
与门个数 | 3 | 4 | 8 | 16 | 64 | 256 | … | P P P |
与门输入端个数 | 2 | 3 | 7 | 15 | 63 | 255 | … | P − 1 P-1 P−1 |
比较对法总结
- 块数少时,所需要的硬件较少,
- 随着组内块数
P
的增加,所需的触发器的个数会以平方的关系迅速增加,门的输入端数也线性增加。 - (硬件实现的成本很高)
7.2.5 写策略
写策略是区分不同Cache设计方案的一个重要标志。
- “写”操作必须在确认是命中后才可进行
- “写”访问有可能导致Cache和主存内容的不一致
两种写策略
- 写直达法(也称为存直达法)
- 执行“写”操作时,不仅写入Cache,而且也写入下一级存储器。
- 写回法(也称为拷回法)
- 执行“写”操作时,只写入Cache。仅当Cache中相应的块被替换时,才写回主存。 (设置“修改位”)
两种写策略的比较
- 写回法的优点:速度快,所使用的存储器带宽较低。
- 写直达法的优点:易于实现,一致性好(下一级存储器的数据总是最新,对I/O和多处理机来说很重要)。
CPU写停顿
采用写直达法时,若在进行“写”操作的过程中CPU必须等待,直到“写”操作结束,则称CPU写停顿。
减少写停顿的一种常用的优化技术:采用写缓冲器
- CPU一旦把数据写入该缓冲器,就可以继续执行,从而使下一级存储器的更新和CPU的执行重叠
“写”操作时的调块
当发生写不命中时,是否调入相应的块,有两种选择
-
按写分配(写时取)
写不命中时,先把所写单元所在的块调入Cache,再行写入。
-
不按写分配(绕写法)
写不命中时,直接写入下一级存储器而不调块。
两种写策略采用的方式
- 写回法 ── 按写分配
- 写直达法 ── 不按写分配
7.2.6 Cache的性能分析
使用
不命中率
评价存储系统性能非常方便,但是也容易产生误导。平均访存时间
是一种更好的评测存储系统性能的指标。
平均访存时间
平均访存时间 = 命中时间+不命中率×不命中开销
\text{平均访存时间 = 命中时间+不命中率×不命中开销}
平均访存时间 = 命中时间+不命中率×不命中开销
- 平均访存时间的两个部分可以用
绝对时间
(如命中时间为20ns),或者是时钟周期数
来衡量
不命中开销
不命中开销=不命中周期数(停顿)× 时钟周期时间
\text{不命中开销=不命中周期数(停顿)× 时钟周期时间}
不命中开销=不命中周期数(停顿)× 时钟周期时间
平均访存时间不能代替程序执行时间。执行一个程序的
CPU时间
能更好的反映存储系统的性能。
程序执行时间
CPU时间=(CPU执行周期数+存储器停顿周期数)× 时钟周期时间
\text{CPU时间=(CPU执行周期数+存储器停顿周期数)× 时钟周期时间}
CPU时间=(CPU执行周期数+存储器停顿周期数)× 时钟周期时间
其中:
-
存 储 器 停 顿 时 钟 周 期 数 = “ 读 ” 的 次 数 × 读 不 命 中 率 × 读 不 命 中 开 销 + “ 写 ” 的 次 数 × 写 不 命 中 率 × 写 不 命 中 开 销 存储器停顿时钟周期数=“读”的次数×读不命中率×读不命中开销+“写”的次数×写不命中率×写不命中开销 存储器停顿时钟周期数=“读”的次数×读不命中率×读不命中开销+“写”的次数×写不命中率×写不命中开销
-
存 储 器 停 顿 时 钟 周 期 数 = 访 存 次 数 × 不 命 中 率 × 不 命 中 开 销 存储器停顿时钟周期数=访存次数×不命中率×不命中开销 存储器停顿时钟周期数=访存次数×不命中率×不命中开销
因此,可以得到以下公式:
C
P
U
时间
=
(
C
P
U
执行周期数
+
访存次数
×
不命中率
×
不命中开销
)
×
时钟周期时间
=
I
C
×
(
C
P
I
e
x
e
c
u
t
i
o
n
+
访存次数
指令数
×
不命中率
×
不命中开销
)
×
时钟周期时间
=
I
C
×
(
C
P
I
e
x
e
c
u
t
i
o
n
+
每条指令的平均访存次数
×
不命中率
×
不命中开销
)
×
时钟周期时间
\begin{aligned} CPU\text{时间}&=(CPU\text{执行周期数} + \text{访存次数} × \text{不命中率} × \text{不命中开销})× \text{时钟周期时间} \\ &= IC × \left(CPI_{execution} + \frac{\text{访存次数}}{\text{指令数}} × \text{不命中率} × \text{不命中开销} \right) × \text{时钟周期时间} \\ &= IC × \left(CPI_{execution} + \text{每条指令的平均访存次数} × \text{不命中率} × \text{不命中开销} \right) × \text{时钟周期时间} \end{aligned}
CPU时间=(CPU执行周期数+访存次数×不命中率×不命中开销)×时钟周期时间=IC×(CPIexecution+指令数访存次数×不命中率×不命中开销)×时钟周期时间=IC×(CPIexecution+每条指令的平均访存次数×不命中率×不命中开销)×时钟周期时间
一些结论
- Cache不命中对于一个
CPI较小
而时钟频率较高
的CPU来说,影响是双重的:- C P I e x e c u t i o n CPI_{execution} CPIexecution越低,固定周期数的Cache不命中开销的相对影响就越大。
- 在计算CPI时,不命中开销的单位是时钟周期数。因此,即使两台计算机的存储层次完全相同,时钟频率较高的CPU的不命中开销较大,其CPI中存储器停顿这部分也就较大。
- 因此Cache对于
低CPI、高时钟频率
的CPU来说更加重要
。
例题——比较平均访存时间和CPU执行时间
考虑两种不同组织结构的Cache:直接映象Cache和两路组相联Cache,试问它们对CPU性能有何影响?先求平均访存时间,然后再计算CPU性能。分析时请用以下假设:
- 理想Cache(命中率为100%)情况下的
CPI
为2.0
,时钟周期
为2ns
,平均每条指令访存1.3次
。- 两种Cache容量均为64KB,块大小都是32字节。
- 在组相联Cache中,由于多路选择器的存在而使CPU的时钟周期增加到原来的
1.10倍
。这是因为对Cache的访问总是处于关键路径上,对CPU的时钟周期有直接的影响。- 这两种结构Cache的
不命中开销
都是70ns
。(在实际应用中,应取整为整数个时钟周期)- 命中时间为1个时钟周期,64KB直接映象Cache的
不命中率
为1.4%
,相同容量的两路组相联Cache的不命中率
为1.0%
。
解答:
平均访存时间公式为:平均访存时间=命中时间+不命中率×不命中开销
两种结构的平均访存时间分别为:
平均访存时间
1路
=
2.0
+
(
1.4
%
×
70
)
=
2.98
(
n
s
)
平均访存时间
2路
=
2.0
×
1.10
+
(
1.0
%
×
70
)
=
2.90
(
n
s
)
\begin{aligned} \text{平均访存时间}_{\text{1路}} &= 2.0 + (1.4\% × 70) = 2.98(ns) \\ \text{平均访存时间}_{\text{2路}} &= 2.0 × 1.10 + (1.0\% × 70) = 2.90(ns) \end{aligned}
平均访存时间1路平均访存时间2路=2.0+(1.4%×70)=2.98(ns)=2.0×1.10+(1.0%×70)=2.90(ns)
两路组相联Cache的平均访存时间比较低。
CPU性能为:
C
P
U
时间
=
(
C
P
U
执行周期数
+
访存次数
×
不命中率
×
不命中开销
)
×
时钟周期时间
=
I
C
×
(
C
P
I
e
x
e
c
u
t
i
o
n
+
访存次数
指令数
×
不命中率
×
不命中开销
)
×
时钟周期时间
=
I
C
×
(
C
P
I
e
x
e
c
u
t
i
o
n
+
每条指令的平均访存次数
×
不命中率
×
不命中开销
)
×
时钟周期时间
\begin{aligned} CPU\text{时间}&=(CPU\text{执行周期数} + \text{访存次数} × \text{不命中率} × \text{不命中开销})× \text{时钟周期时间} \\ &= IC × \left(CPI_{execution} + \frac{\text{访存次数}}{\text{指令数}} × \text{不命中率} × \text{不命中开销} \right) × \text{时钟周期时间} \\ &= IC × \left(CPI_{execution} + \text{每条指令的平均访存次数} × \text{不命中率} × \text{不命中开销} \right) × \text{时钟周期时间} \end{aligned}
CPU时间=(CPU执行周期数+访存次数×不命中率×不命中开销)×时钟周期时间=IC×(CPIexecution+指令数访存次数×不命中率×不命中开销)×时钟周期时间=IC×(CPIexecution+每条指令的平均访存次数×不命中率×不命中开销)×时钟周期时间
其中:不命中开销×时钟周期时间为70ns,
C
P
I
e
x
e
c
u
t
i
o
n
=
2
CPI_{execution}=2
CPIexecution=2,时钟周期时间分别为2/2*1.1,每条指令的平均访存次数为1.3,代入可得:
CPU时间
1路
=
I
C
×
[
2.0
×
2
+
(
1.3
×
1.4
%
×
70
)
]
=
5.27
×
I
C
CPU时间
2路
=
I
C
×
[
2.0
×
1.10
×
2
+
(
1.3
×
1.0
%
×
70
)
]
=
5.31
×
I
C
\begin{aligned} \text{CPU时间}_{\text{1路}} &= IC × [2.0×2 + (1.3×1.4\%×70)] = 5.27×IC\\ \text{CPU时间}_{\text{2路}} &= IC × [2.0×1.10×2 + (1.3×1.0\%×70)] = 5.31×IC \end{aligned}
CPU时间1路CPU时间2路=IC×[2.0×2+(1.3×1.4%×70)]=5.27×IC=IC×[2.0×1.10×2+(1.3×1.0%×70)]=5.31×IC
相对性能比为:
CPU时间
2路
CPU时间
1路
=
5.13
×
I
C
5.27
×
I
C
=
5.31
5.27
=
1.01
\frac{\text{CPU时间}_{\text{2路}}}{\text{CPU时间}_{\text{1路}}} = \frac{5.13 × IC}{5.27 × IC} = \frac{5.31}{5.27} = 1.01
CPU时间1路CPU时间2路=5.27×IC5.13×IC=5.275.31=1.01
结论:
- 和平均访存时间的比较结果相反,直接映像Cache的平均性能稍好一些,这是因为在两路组相联的情况下,虽然不命中次数减少了,但所有指令的时钟周期时间都增加了10%。
- 由于CPU时间是我们进行评价的基准,而且直接映像Cache的实现更简单,所以本例中直接映像Cache是较好的选择。
7.2.7 如何改进Cache的性能
平均访存时间=命中时间+不命中率×不命中开销
可以看出改进Cache性能可以从三方面入手
可以从三个方面
改进Cache的性能:
- 降低不命中率
- 减少不命中开销
- 减少Cache命中时间