文章目录
第7章 存储系统
7.4 减少Cache不命中开销
7.4.1 采用两级Cache
在Cache和主存之间进行改进,不影响CPU。
思想:
- 在原有Cache和存储器之间增加一级Cache,构成两级Cache。
- 第一级Cache做的足够小,速度和CPU匹配
- 第二级Cache做的足够大,捕获更多本来要对主存的访问,从而降低实际不命中开销
性能分析
- 原有的
平均访存时间
公式变为以下形式:
平均访存时间 = 命中时间 L 1 + 不命中率 L 1 × 不命中开销 L 1 \text{平均访存时间}= \text{命中时间}_{L_{1}} + \text{不命中率}_{L_{1}} × \text{不命中开销}_{L_{1}} 平均访存时间=命中时间L1+不命中率L1×不命中开销L1
不命中开销 L 1 = 命中时间 L 2 + 不命中率 L 2 × 不命中开销 L 2 \text{不命中开销}_{L_{1}}= \text{命中时间}_{L_{2}}+\text{不命中率}_{L_{2}}× \text{不命中开销}_{L_{2}} 不命中开销L1=命中时间L2+不命中率L2×不命中开销L2
- 合并可得:
平均访存时间 = 命中时间 L 1 + 不命中率 L 1 × ( 命中时间 L 2 + 不命中率 L 2 × 不命中开销 L 2 ) \text{平均访存时间}= \text{命中时间}_{L_{1}} + \text{不命中率}_{L_{1}} × (\text{命中时间}_{L_{2}}+\text{不命中率}_{L_{2}}× \text{不命中开销}_{L_{2}}) 平均访存时间=命中时间L1+不命中率L1×(命中时间L2+不命中率L2×不命中开销L2)
- 另一种形式的
平均访存时间
:
T e f f = h 1 t 1 + ( 1 − h 1 ) h 2 t 2 + ( 1 − h 1 ) ( 1 − h 2 ) h 3 t 3 + ⋯ + ( 1 − h 1 ) ( 1 − h 2 ) ⋯ ( 1 − h n − 1 ) h n t n T_{eff} = h_{1}t_{1} + (1 - h_{1})h_{2}t_{2} + (1-h_{1})(1-h_{2})h_{3}t_{3} + \cdots + (1-h_{1})(1-h_{2})\cdots(1-h_{n-1})h_{n}t_{n} Teff=h1t1+(1−h1)h2t2+(1−h1)(1−h2)h3t3+⋯+(1−h1)(1−h2)⋯(1−hn−1)hntn
其中:
h
i
h_{i}
hi是存储器层次结构中任意两个相邻层当在
M
i
M_{i}
Mi找到某一信息项时的概率,称为命中率
。
局部不命中率与全局不命中率
局部不命中率
=
该级Cache的不命中次数
/
到达该级Cache的访问次数
\text{局部不命中率}= \text{该级Cache的不命中次数}\ /\ \text{到达该级Cache的访问次数}
局部不命中率=该级Cache的不命中次数 / 到达该级Cache的访问次数
全局不命中率 = 该级Cache的不命中次数 / CPU发出的访存的总次数 \text{全局不命中率}=\text{该级Cache的不命中次数}\ /\ \text{CPU发出的访存的总次数} 全局不命中率=该级Cache的不命中次数 / CPU发出的访存的总次数
说明:
-
全局不命中率是比局部不命中率更好的衡量指标,指出来在CPU发出的访存中,有多大比例是穿过各级Cache,最终到达存储器的
-
评价第二级Cache时,应使用全局不命中率这个指标
全局不命中率 L 2 = 不命中率 L 1 × 不命中率 L 2 \text{全局不命中率}_{L_{2}}=\text{不命中率}_{L_{1}} × \text{不命中率}_{L_{2}} 全局不命中率L2=不命中率L1×不命中率L2
每条指令的平均访存停顿时间
每条指令的平均访存停顿时间
=
每条指令的平均不命中次数
L
1
×
命中时间
L
2
+
每条指令的平均不命中次数
L
2
×
不命中开销
L
2
\text{每条指令的平均访存停顿时间} = \text{每条指令的平均不命中次数}_{L_{1}} × \text{命中时间}_{L_{2}}+\text{每条指令的平均不命中次数}_{L_{2}}×\text{不命中开销}_{L_{2}}
每条指令的平均访存停顿时间=每条指令的平均不命中次数L1×命中时间L2+每条指令的平均不命中次数L2×不命中开销L2
实例1
:考虑某一两级Cache:第一级Cache为L1,第二级Cache为L2。
假设在1000次访存中,L1的不命中是40次,L2的不命中是20次。求各种局部不命中率和全局不命中率。
假设L2的命中时间是10个时钟周期,L2的不命中开销是100时钟周期,L1的命中时间是1个时钟周期,平均每条指令访存1.5次,不考虑写操作的影响。
问:平均访存时间是多少?每条指令的平均停顿时间是多少个时钟周期?
第一问:
-
第一级Cache的不命中率:
- 全局、局部: 40 1000 = 4 % \frac{40}{1000} = 4 \% 100040=4%
-
第二级Cache的不命中率:
- 局部: 20 40 = 50 % \frac{20}{40} = 50\% 4020=50%
- 全局: 20 1000 = 2 % \frac{20}{1000} = 2\% 100020=2%
第二问:
由以下公式可知:
平均访存时间
=
命中时间
L
1
+
不命中率
L
1
×
(
命中时间
L
2
+
不命中率
L
2
×
不命中开销
L
2
)
\text{平均访存时间}= \text{命中时间}_{L_{1}} + \text{不命中率}_{L_{1}} × (\text{命中时间}_{L_{2}}+ \text{不命中率}_{L_{2}}× \text{不命中开销}_{L_{2}})
平均访存时间=命中时间L1+不命中率L1×(命中时间L2+不命中率L2×不命中开销L2)
-
命中时间 L 1 = 1 \text{命中时间}_{L_{1}} = 1 命中时间L1=1
-
不命中率 L 1 = 4 % \text{不命中率}_{L_{1}} = 4\% 不命中率L1=4%
-
命中时间 L 2 = 10 \text{命中时间}_{L_{2}} = 10 命中时间L2=10
-
不命中率 L 2 = 50 % \text{不命中率}_{L_{2}} = 50\% 不命中率L2=50%
-
不命中开销 L 2 = 100 \text{不命中开销}_{L_{2}} = 100 不命中开销L2=100
平均访存时间 = 1 + 4 % × ( 10 + 50 % × 100 ) = 3.4 时钟周期 \text{平均访存时间}= 1 + 4\% × (10 + 50\% × 100) = 3.4 \text{时钟周期} 平均访存时间=1+4%×(10+50%×100)=3.4时钟周期
由于平均平均每条指令访存1.5次,每条指令的平均访存停顿时间为:
3.4
−
1
=
2.4
时钟周期
3.4 - 1 = 2.4 \text{时钟周期}
3.4−1=2.4时钟周期,所以:
每
条
指
令
的
平
均
停
顿
时
间
=
2.4
×
1.5
=
3.6
时钟周期
每条指令的平均停顿时间 = 2.4 × 1.5 = 3.6\text{时钟周期}
每条指令的平均停顿时间=2.4×1.5=3.6时钟周期
第二级Cache的相关结论:
- 在第二级Cache比第一级 Cache大得多的情况下,两级Cache的全局不命中率和容量与第二级Cache相同的单级Cache的不命中率非常接近。
- 在评价第二级Cache时,应用全局不命中率这个指标。
第二级Cache不会影响CPU的时钟频率,因此其设计有更大的考虑空间。
第一级Cache和第二级Cache之间的首要区别
- 第一级Cache的速度会影响CPU的时钟频率,而第二级Cache的速度只影响第一级Cache的不命中开销。
- 因此,在设计第二级Cache时可以有更多的考虑空间,许多不适合于第一级Cache的方案对于第二级Cache可以使用
设计第二级Cache两个问题:
- 它能否降低CPI中的平均访存时间部分?
- 它的成本是多少?
第二级Cache的参数
- 容量
- 第二级Cache的容量一般比第一级的大许多。大容量意味着第二级Cache可能实际上没有容量
不命中,只剩下一些强制性不命中和冲突不命中。
- 第二级Cache的容量一般比第一级的大许多。大容量意味着第二级Cache可能实际上没有容量
- 相联度
- 第二级Cache可采用较高的相联度或伪相联方法。
实例2
:如果有一个二级Cache,请计算和分析如下问题:给出有关第二级Cache的以下数据:
- 对于直接映象,命中时间 L 2 = 10 L_{2} = 10 L2=10个时钟周期
- 两路组相联使命中时间增加0.1个时钟周期,即为10.1个时钟周期。
- 对于直接映象,局部不命中率 L 2 = 25 % L_{2} = 25\% L2=25%
- 对于两路组相联,局部不命中率 L 2 = 20 % L_{2}= 20\% L2=20%
- 不命中开销 L 2 = 50 L_{2} = 50 L2=50个时钟周期
通过计算和分析说明第二级Cache的相联度对不命中开销的影响如何?
根据公式: 不命中开销 L 1 = 命中时间 L 2 + 不命中率 L 2 × 不命中开销 L 2 \text{不命中开销}_{L_{1}}= \text{命中时间}_{L_{2}}+\text{不命中率}_{L_{2}}× \text{不命中开销}_{L_{2}} 不命中开销L1=命中时间L2+不命中率L2×不命中开销L2
- 对一个直接映象的第二级Cache来说,第一级Cache的不命中开销为:
不命中开销 直接映象 , L 1 = 10 + 25 % × 50 = 22.5 个时钟周期 \text{不命中开销}_{\text{直接映象}, L_{1}}= 10 + 25 \% × 50 = 22.5 \text{个时钟周期} 不命中开销直接映象,L1=10+25%×50=22.5个时钟周期
- 对于两路组相联第二级Cache来说,命中时间增加了10%*1个时钟周期,故第一级Cache的不命中开销为:
不命中开销 两路组相联 , L 1 = 10 ∗ 1.1 + 20 % × 50 = 20.1 个时钟周期 \text{不命中开销}_{\text{两路组相联}, L_{1}}= 10*1.1 + 20 \% × 50 = 20.1 \text{个时钟周期} 不命中开销两路组相联,L1=10∗1.1+20%×50=20.1个时钟周期
由于在实际机器中,第二级Cache几乎总是和第一级Cache以及CPU同步的。因此,第二级Cache的命中时间必须是时钟周期的整数倍。这里把该命中时间取整为11个时钟周期,即:
不命中开销
两路组相联
,
L
1
=
21
个时钟周期
\text{不命中开销}_{\text{两路组相联}, L_{1}}= 21 \text{个时钟周期}
不命中开销两路组相联,L1=21个时钟周期
不难看出,两路组相联比直接映像的效果更好
降低第二级cache的不命中率
可以采用提高相联度
和伪相联方法
来减少第二级Cache的不命中率
,从而达到减少不命中开销的目的,因为:
- 它们对第二级的命中时间影响很小,而且平均访存时间中很大一部分是由于第二级Cache的不命中而产生的。
- 虽然较大容量的第二级Cache消除了一些
冲突不命中
(因块数增加了),但它同时也减少了容量不命中
,所以在直接映像的第二级Cache中,冲突不命中所占的比例依然很大。
对于第二级Cache来说,同样也可以采用增加块大小
的方法来减少其不命中率。前面我们已经得出这样的结论:
- Cache 块的大小增加到一定程度后,反而可能导致不命中率上升。
- 但对于大容量的第二级Cache来说,这一点并不成为问题,因为它容量大,使其不命中率达到最低的块大小也比较大。64 字节、128字节甚至256字节的块大小都是第二级Cache经常采用的。
第二级Cache的多级包容性
多级包容性
:第一级Cache中的数据总是同时存在于第二级Cache中
作用
:多级包容性有利于实现I/O和Cache之间的内容一致性检测。
为了减少平均访存时间,可以让容量较小的第一级Cache采用较小的块,而让容量较大的第二级Cache采用较大的块。在这种情况下,仍可实现包容性,但在处理第二级Cache不命中时要做以下工作:
- 替换第二级Cache中的块时,必须作废所有对应于该块的第一级Cache中的块。这样不但会使第一级Cache的不命中率有所增加,而且会造成不必要的作废操作。
- 如果结合使用其他一些性能优化技术(如非阻塞的第二级Cache),包容性就会进一步增加复杂度。
总结
-
Cache设计的本质
是在快速命中
和减少不命中次数
这两个方面进行权衡。 -
大部分优化措施都是在提高一方的同时损害另一方。对于第二级Cache而言:
- 由于它的命中次数比第一级Cache少得多,所以重点就放在减少其不命中次数上。
- 这就导致了更大容量、更高相联度和块更大的Cache的出现。
7.4.2 让读不命中优先于写
背景:在写直达Cache中,每次写访问都要对主存进行写入。为了提高性能,一般都是设置一个大小适中的写缓冲器,CPU将数据写入写缓冲器后即可继续执行后序指令。
存在的问题:写缓冲器导致存储器访问的复杂化
- 在读不命中时,所读单元的最新值有可能还在写缓冲器中,尚未写入主存。
解决方法一:
-
最简单的办法是
推迟对读不命中的处理
,直至写缓冲器清空。 -
缺点:由于在发生读不命中时,写缓冲器中几乎总是有数据的,这就增加了处理读不命中的开销。
解决方法二:
- 在读不命中时
检查写缓冲器的内容
,如果没有冲突
(即没有地址相同)而且存储器可访问,就可继续处理读不命中。即让读不命中优先于写
。
在写回法Cache的应用
在写回法Cache中,也可以利用写缓冲器来提高性能,假定读不命中将替换一个修改过的存储块:
-
不先把该块写回存储器,然后再从读存储器调块。
-
而是把被替换的块临时复制到一个缓冲器中,然后从存储器调块,最后再把缓冲器中的内容写入存储器。
-
这样CPU的读访问就能更快地完成了。
和上面的情况类似,发生读不命中时,处理器可以采用:
- 等待缓冲区清空的方法
- 采用检查与缓冲器中各字的地址是否有冲突的方法
7.4.3 写缓冲合并
背景:
为了减少写访问所花的时间,写直达Cache一般都采用一个写缓冲器。如果该缓冲器不满,就可以把数据和相应地址写人该缓冲器。
从CPU的角度来看,这个写操作就算是完成了,CPU可以继续执行后面的指令,而写缓冲器则负责将其写入存储器。
写缓冲合并
- 在写缓冲器不为空的情况下,则需要把这次的写入地址与写缓冲器中已有的所有地址进行比较,看是否有匹配的项。
- 如果有地址匹配而对应的位置又是空闲的,就把这次要写入的数据与该项合并。
- 如果写缓冲器满且没有能进行写合并的项,则等待。
优点:
- 提高了写缓冲器的空间利用率
- 减少因写缓冲器满而等待的时间。
下图给出了采用和不采用写合并的例子。假设写缓冲器有4项,每项能够存放4个64位的字。图中V
代表有效位。
- 当不采用写合并时,写入4个连续存放的数据,就会使写缓冲器满了,
3/4
的空间被浪费。 - 采用写合并时,则只需占用一个项。
7.4.4 请求字处理技术
与前面减少不命中开销的方法不同,请求字处理技术
不用增加硬件
。
请求字
- 从下一级存储器调入Cache的块中,只有一个字是立即需要的。这个字称为
请求字
。
当CPU所请求的字到达后,不等整个块都调入Cache,就可把该字发送给CPU并重启CPU继续执行
有两种具体的方案:
- 尽早重启动:在请求字没有到达时,CPU处于等待状态。一旦请求字到达,就立即发送给CPU,让CPU尽早重启动,继续执行。
- 请求字优先:调块时,让存储器首先提供CPU所要的请求字,请求字一旦到达,就立即送给CPU,让CPU继续执行。同时从存储器调入该块的其余部分。
说明:
- 一般来说,仅当Cache块很大时才有效。
- 因为当Cache块较小时,是否使用这些技术,不命中开销差别不大。
- 此外,在采用请求字优先时,若下一条指令正好访问Cache块的另一部分(以请求字为界,该Cache块被分为两部分),则只能节省一个时钟周期,因为只有得到请求字的指令在流水线中可以继续前进,下一条指令还是必须停下来等待所需的数据。
7.4.5 非阻塞Cache技术
非阻塞Cache
:Cache不命中时仍允许CPU进行其它的命中访问。即允许“不命中下命中”。
进一步提高性能(理论上):
- 让 Cache允许多个不命中重叠
- 支持“多重不命中下的命中"和“不命中下的不命中",则可进一步减少实际不命中开销。
⚠️说明:
- 可以同时处理的不命中次数越多,所能带来的性能上的提高就越大。但这并不意味着不命中次数越多越好。
- 非阻塞Cache大大增加了Cache控制器的复杂度,特别是多重叠的非阻塞Cache,因此,在设计时要做全面综合考虑。
7.4.6 降低Cache不命中开销技术总结
为了描述方便,引入以下记号:
“+”号
:表示改进了相应指标。“-”号
:表示它使该指标变差。空格栏
:表示它对该指标无影响。复杂性
:0表示最容易,3表示最
优化技术 | 不命中率 | 不命中开销 | 命中时间 | 硬件复杂度 | 说明 |
---|---|---|---|---|---|
使得读不命中优先于写 | + | - | 1 | 在单处理机上实现容易,被广泛采用 | |
写缓冲合并 | + | 1 | 与写直达合用,广泛应用,例如21164,UltraSPARC Ⅲ | ||
两级Cache | + | 2 | 硬件代价大;两级Cache的块大小不同时实现困难;被广泛采用 | ||
请求字处理技术 | + | 2 | 被广泛采用 | ||
非阻塞Cache | + | 3 | 在支持乱序执行的CPU中使用 |