![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-blog.csdnimg.cn/direct/7b0a9eee44894a2bac47e84dba1ef82c.png)
一、存储体系基础知识回顾
详细内容见计算机组成与设计:硬件/软件接口,第五章详细梳理,附思维导图_硬件接口梳理-CSDN博客
下面仅简单回顾
1.1 存储层次
存储划分为层次结构原因:人们要求容量大、速度快、价格低,这三个指标是相互矛盾的。
往上:快贵小 往下:慢廉大
- L1~L3:Cache
- L4:内存
- L5:二级存储器或辅存
1.2 局部性原理
**时间局部性:**程序马上将要用到的信息很可能就是现在正在使用的信息。
**空间局部性:**程序马上将要用到的信息很可能与现在正在使用的信息在存储空间上是相邻的。
1.3 存储器技术
高速缓存采用:**SRAM:**使用双稳态触发器
内存采用:**DRAM:**电容保存电荷原理
辅存采用:闪存和磁盘
1.4 Cache
1.4.1 映射方式
- 直接映射
- 组相联
- 全相联
1.4.2 查找方法
直接映射:内存地址划分为:标记位-块号-块内字节偏移,找到块号然后比较标记位
组相联映射:内存地址划分为:标记位-块号-块内字节偏移,找到组,在组内遍历每个块比较标记位
全相联映射:内存地址划分为:标记位-块内字节偏移,直接和每一块作比较
1.4.3 替换算法
- 随机替换:实现简单
- 先进先出FIFO
- LRU:逐出最近最久未被使用的
- LFU:逐出最不经常使用的
1.4.4 写策略
- 写命中:
- 写直达:写Cache的同时写回内存
- 改进方法:写缓冲
- 写回:只写Cache,当Cache中块被替换且数据修改过,那么将数据写回内存
- 写直达:写Cache的同时写回内存
- 写不命中
- 按写分配:写不命中时,先将对应块调入Cache,再行写入操作
- 不按写分配:直接进入下一级存储器而不调块
一般来说:
写直达——不按写分配
写回——按写分配
1.4.5 性能
- 不命中率与实际CPI
- 平均访存时间
- 命中时间
- 不命中率
- 缺失代价
1.5 虚拟内存
用户看来自己似乎有着无限大的内存。
1.5.1 地址转换
磁盘和内存间交换——分页存储
虚拟地址划分:页号-页内偏移
虚拟地址转换物理地址:页号查询页表,找到物理页号,然后和页内偏移进行拼接得到物理地址
1.5.2 页表
每个进程都拥有一张页表, 存放在自己的内存空间
页表首地址存放在页表寄存器中
页表项记录了每个虚拟地址对应的物理地址
页表项:有效位 - 脏位 - 引用位 -(LRU)- 物理页号
1.5.3 TLB
TLB是页表的“Cache”,因而TLB中保存页表全部信息——三个标志位和物理页号。
TLB以虚拟页号为标记位
二、Cache性能分析与改进
2.1 Cache 性能分析
-
不命中率:
- 与硬件速度无关
- 容易产生一些误导
-
平均访存时间
- 平均访存时间 = 命中时间 + 失效率 × 失效开销
-
程序执行时间
- C P U T i m e = ( 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{align} CPUTime &= (CPU执行周期数+存储器停顿周期数)\times 时钟周期时间 \\ &= IC\times (CPI_{execution} + \frac{访存次数}{指令数}\times 不命中率\times 不命中开销)\times 时钟周期时间 \\ &= IC\times (CPI_{execution} + 每条指令的平均访存次数\times 不命中率\times 不命中开销)\times 时钟周期时间 \end{align} CPUTime=(CPU执行周期数+存储器停顿周期数)×时钟周期时间=IC×(CPIexecution+指令数访存次数×不命中率×不命中开销)×时钟周期时间=IC×(CPIexecution+每条指令的平均访存次数×不命中率×不命中开销)×时钟周期时间
例:用一个和 Alpha AXP 类似的机器作为第一个例子。假设Cache不命中开销为50个时钟周期,当不考虑存储器停顿时,所有指令的执行时间都是2.0个时钟周期,访问Cache不命中率为2%,平均每条指令访存1.33次。试分析Cache对性能的影响。
C P U T i m e = I C × ( C P I e x e c u t i o n + 每条指令的平均访存次数 × 不命中率 × 不命中开销 ) × 时钟周期时间 = I C × ( 2 + 1.33 × 2 % × 50 ) × 时钟周期时间 = I C × 3.33 × 时钟周期时间 实际 C P I 为原来的 3.33 2.0 = 1.67 倍 但如果不使用 C a c h e ,实际 C P I = 2.0 + 50 × 1.33 = 68.5 \begin{align} CPUTime &= IC\times (CPI_{execution} + 每条指令的平均访存次数\times 不命中率\times 不命中开销)\times 时钟周期时间 \\ &= IC \times (2 + 1.33\times 2\% \times 50)\times 时钟周期时间 \\ &= IC\times 3.33\times 时钟周期时间 \\ &实际CPI为原来的\frac{3.33}{2.0} = 1.67倍\\ &但如果不使用Cache,实际CPI=2.0+50\times 1.33 = 68.5 \end{align} CPUTime=IC×(CPIexecution+每条指令的平均访存次数×不命中率×不命中开销)×时钟周期时间=IC×(2+1.33×2%×50)×时钟周期时间=IC×3.33×时钟周期时间实际CPI为原来的2.03.33=1.67倍但如果不使用Cache,实际CPI=2.0+50×1.33=68.54.Cache不命中对于一个CPI较小而时钟频率较高的CPU而言,影响是双重的。
- $ CPI _{execution} $越低,固定周期数的Cache不命中开销的相对影响越大
- 在计算CPI时,失效开销的单位是时钟周期数因此,即使两台计算机的存储层次完全相同,时钟频率较高的CPU的失效开销较大(因为访存速度跟不上CPU速度,导致了“存储墙”),其CPI中存储器停顿这部分也就较大
因此 Cache 对于低CPI、高时钟频率的CPU来说更加重要
例
解:
$ 平均访存时间_{1路} = 2 + 1.4% \times 70 = 2.98ns $
$平均访存时间_{2路} = 2\times 1.1% + 1.0% \times 70 = 2.90ns $
两路组相联Cache的平均访存时间比较低.
$ CPUTime_{1路}=IC\times (2.0\times 2 + (1.3\times 1.4% \times 70)=5.27\times IC $
$CPUTime_{2路}=IC\times (2.0\times 2\times 1.10 + (1.3\times 1.0% \times 70)=5.31\times IC $
可见直接映像Cache平均性能好一些
2.2 改进Cache的性能
- $ 平均访存时间 = 命中时间 + 不命中率 \times 不命中开销 $
- 可以从三个方面改进Cache的性能
- 降低不命中率
- 减少不命中开销
- 减少Cache命中时间
- 下面介绍17种Cache优化技术
- 8种 用于降低不命中率
- 5种 用于减少不命中开销
- 4种 用于减少命中时间
三、降低不命中率
3.1 三种类型不命中
- 强制性不命中(Compulsory miss)
- 当第一次访问一个块时,该块不在Cache中,需从下级存储器中调入Cache,这就是强制性不命中。(冷启动不命中,首次访问不命中)
- 容量不命中(Capacity miss)
- 如果程序执行时所需的块不能全部调入Cache中,则当某些块被替换后,若又重新被访问,就会发生不命中。这种不命中称为容量不命中。
- 冲突不命中(Conflict miss)
- 在组相联或直接映象Cache中,若太多的块映象到同一组(块)中,则会出现该组中某个块被别的块替换(即使别的组或块有空闲位置),然后又被重新访问的情况。这就是发生了冲突不命中。(碰撞不命中,干扰不命中)
3.2 三种类型不命中所占比例
可以发现:
- 相联度越高,冲突不命中就越少
- 强制性不命中和容量不命中不受相联度的影响
- 强制性不命中不受Cache容量的影响,但容量不命中却随着容量的增加而减少。
减少三种不命中的方法
- 强制性不命中: 增加块大小、预取。
- 容量不命中:增加容量
- 冲突不命中:提高相联度
许多降低不命中率的方法会增加命中时间或不命中开销
3.3 降低不命中率的8种方法
3.3.1 增加Cache块大小——降低冷启动缺失
-
不命中率与块大小的关系
- 对于给定的Cache容量,当块大小增加时,不命中率开始是下降,后来反而上升了。原因:
- 一方面它减少了强制性不命中;
- 另一方面,由于增加块大小会减少Cache中块的数目,所以有可能会增加冲突不命中。
- 对于给定的Cache容量,当块大小增加时,不命中率开始是下降,后来反而上升了。原因:
-
增加块大小会增加不命中开销
3.3.2 增加Cache的容量——降低容量不命中
- 最直接的方法是增加Cache的容量
- 缺点:增加成本、可能增加命中时间
- 这种方法在片外Cache中用得比较多
3.3.3 提高相联度——降低冲突缺失
- 采用相联度超过8的方案的实际意义不大。
- 2 : 1 Cache经验规则
- 容量为N的直接映象Cache的不命中率和容量为 N / 2 的两路组相联Cache的不命中率差不多相同。
- 提高相联度是以增加命中时间为代价
3.3.4 “牺牲”Cache
前面三种方法虽然降低了某种缺失率,但是却又增加了其他性能指标的成本
“牺牲”Cache:一种能减少冲突不命中次数而又不影响时钟频率的方法,
基本思想
- 在Cache和它从下一级存储器调数据的通路之间设置一个全相联的小Cache,称为“牺牲”Cache(Victim Cache)。用于存放被替换出去的块(称为牺牲者),以备重用
作用
对于减小冲突不命中很有效,特别是对于小容量的直接映象数据Cache,作用尤其明显。
例如:
项数为4的Victim Cache: 能使4KB Cache的冲突不命中减少20%~90%
代价:增加了硬件成本
3.3.5 伪相联Cache(列相联Cache)
直接映像和组相联的特点:
**伪相联Cache 基本思想:**在逻辑上把直接映象Cache的空间上下平分为两个区。对于任何一次访问,伪相联Cache先按直接映象Cache的方式去处理。若命中,则其访问过程与直接映象Cache的情况一样。若不命中,则再到另区相应的位置去查找。若找到,则发生了伪命中,否则就只好访问下级存储器。
上下两组具平行关系,第一组没找到就找下一组的平行位置。
访问步骤
- 检查在上半区的映射位置,找到就送给CPU
- 否则,找下半区的平行位置,若找到,送给CPU的同时调换上下区该索引的数据(局部性原理)
- 否则,查下一级存储,调块到下半区,调换上下区该索引的数据(局部性原理)
伪相联:
-
综合多路组相联的低不命中率和直接映象的命中速度
-
伪相联Cache的优点
- 命中时间小
- 不命中率低
-
快速命中与慢速命中
-
缺点
- 多种命中时间——影响CPU频率设计,因此伪相联映射通常用于2级Cache、3级Cache的设计方案中
例:假设当在按直接映象找到的位置处没有发现匹配,而在另一个位置才找到数据**(伪命中)需要2个额外的周期**。问:当Cache容量分别为2KB和128KB时直接映象、2路组相联和伪相联这三种组织结构中,哪一种速度最快?
Cache为2KB时:
Cache为128KB时:
解:
$ 平均访存时间_{伪相联} = 命中时间_{伪相联} + 失效率_{伪相联} \times 失效开销_{伪相联} $
$ 失效率_{伪相联} = 失效率_{2路组相联} $
$ 命中时间_{伪相联} = 命中时间_{1路} + 伪命中率_{伪相联}\times 2 $
$ 伪命中率_{伪相联} = 命中率_{2路} - 命中率_{1路} = 失效率_{1路} - 失效率_{2路} $
综上分析:
$ 平均访存时间_{伪相联} = 命中时间_{1路} + (失效率_{1路} - 失效率_{2路})\times 2+ 失效率_{2路}\times失效开销_{1路} $
代入数据:
$ 平均访存时间_{伪相联,2KB} = 4.844 $
$平均访存时间_{伪相联,128KB} = 1.356 $
两种情况均优于1路和2路
3.3.6 硬件预取
- 指令和数据都可以预取
- 预取内容既可放入Cache,也可放在外缓冲器中。
例如:指令流缓冲器 - 指令预取通常由Cache之外的硬件完成
- 预取效果
- Joppi的研究结果
- 指令预取(4KB,直接映象Cache,块大小 = 16字节)
- 1个块的指令流缓冲器:捕获15%~25%的不命中
- 4个块的指令流缓冲器:捕获50%
- 16个块的指令流缓冲器:捕获72%
- 数据预取(4KB,直接映象Cache)
- 1个数据流缓冲器:捕获25%的不命中
- 还可以采用多个数据流缓冲器
- 指令预取(4KB,直接映象Cache,块大小 = 16字节)
- Palacharla 和 Kessler的研究结果
- 流缓冲器:既能预取指令又能预取数据
- 对于两个64KB四路组相联Cache来说:
- 8个流缓冲器能捕获**50%~70%**的不命中
- Joppi的研究结果
- 预取应利用存储器的空闲带宽,不能影响对正常不命中的处理,否则可能会降低性能。
3.3.7 编译器控制的预取
主要思想:在编译时加入预取指令,在数据被用到之前发出预取请求。
-
按照预取数据所放的位置,可把预取分为两种类型:
- **寄存器预取:**把数据取到寄存器中。
- **Cache预取:**只将数据取到Cache中。
-
按照预取的处理方式不同,可把预取分为:
- **故障性预取:**在预取时,若出现虚地址故障或违反保护权限,就会发生异常。
- 非故障性预取:在遇到这种情况时则不会发生异常,因为这时它会放弃预取,转变为空操作。本节假定Cache预取都是非故障性的,也叫做非绑定预取。
-
在预取数据的同时,处理器应能继续执行。
只有这样,预取才有意义。
非阻塞Cache(非锁定Cache) -
编译器控制预取的目的
使执行指令和读取数据能重叠执行。 -
循环是预取优化的主要对象
不命中开销小时: 循环体展开1~2次
不命中开销大时: 循环体展开许多次 -
每次预取需要花费一条指令的开销
- 保证这种开销不超过预取所带来的收益
- 编译器可以通过把重点放在那些可能会导致不命中的访问上,使程序避免不必要的预取,从而较大程度地减少平均访存时间。
例 对于下面的程序,首先判断哪些访问可能会导致数据Cache失效。然后,加入预取指令以减少失效。最后,计算所执行的预取指令的条数以及通过预取避免的失效次数。假定:
(1)我们用的是一个容量为8KB、块大小为16B的直接映象Cache,它采用写回法并且按写分配。
(2)a、b分别为3x100(3行100列) 和 101x3 的双精度浮点数组,每个元素都是8B。当程序开始执行时,这些数据都不在Cache内。
double a[3][100], b[101][3]; for (i = 0; i < 3; i = i + 1) for (j = 0; j < 100; j = j + 1) a[i][j] = b[j][0] * b[j + 1][0];
考虑a数组按行顺序访问,而块大小为16B,则a的失效次数就是300 / 2 = 150次
b访问第0列,上下两个元素都不在同一块,所以b失效次数为101次
因而初始情况总失效次数就是:251次
改进后的程序:假设失效开销很大,预取必须至少提前7次循环进行。
double a[3][100], b[101][3]; for (j = 0; j < 100; j = j + 1) { prefetch(b[j + 7][0]); prefetch(a[0][j + 7]); a[0][j] = b[j][0] * b[j + 1][0]; } for (i = 1; i < 3; i = i + 1) for (j = 0; j < 100; j = j + 1) { prefetch(a[i][j + 7]); a[i][j] = b[j][0] * b[j + 1][0]; }
考虑a的第一行前7个元素和b第0列的前七个元素不能预取
那么a的失效次数就是 ceil(7 / 2) = 4次,b就是7次
剩下的两层循环中,由于b已经预取完了,所以b不会失效
而a每层i循环会失效4次,因而总的失效次数就是4 * 3 + 7 = 19次
3.3.8 编译器优化
基本思想:通过对软件进行优化来降低不命中率。(**特色:**无需对硬件做任何改动)
- 程序代码和数据重组
- 可以重新组织程序而不影响程序的正确性
- 把一个程序中的过程重新排序,就可能会减少冲突不命中,从而降低指令不命中率。
- McFaring研究了如何使用配置文件(profile)来进行这种优化。
- 把一个程序中的过程重新排序,就可能会减少冲突不命中,从而降低指令不命中率。
- 把基本块对齐,使得程序的入口点与Cache块的起始位置对齐,就可以减少顺序代码执行时所发生的Cache不命中的可能性。(提高大Cache块的效率)
- 如果编译器知道一个分支指令很可能会成功转移,那么它就可以通过以下两步来改善空间局部:
- 将转移目标处的基本块和紧跟着该分支指令后的基本块进行对调
- 把该分支指令换为操作语义相反的分支指令。
- 数据对存储位置的限制更少,更便于调整顺序。
- 可以重新组织程序而不影响程序的正确性
编译优化技术包括
- 数组合并
- 将本来相互独立的多个数组合并成为一个复合数组,以提高访问它们的局部性。
- 内外循环交换
- 循环融合
- 将若干个独立的循环融合为单个的循环。这些循环访问同样的数组,对相同的数据作不同的运算。这样能使得读入Cache的数据在被替换出去之前,能得到反复的使用。
- 分块
1、数组合并
**基本思想:**将本来相互独立的多个数组合并成为一个复合数组,以提高访问它们的局部性。
//修改前
int x[100];
int y[100];//x和y经常一起访问
//修改后
struct merge{
int x, y;
} merged_array[100];
2、内外循环交换
**基本思想:**提高访问的局部性
//修改前
for (j = 0; j < 100; j = j + 1)
for (i = 0; i < 5000; i = i + 1)
x[i][j] = 2 * x[i][j];
//修改后
for (i = 0; i < 5000; i = i + 1)
for (j = 0; j < 100; j = j + 1)
x[i][j] = 2 * x[i][j];
3、循环融合
基本思想: 将若干个独立的循环融合为单个的循环。这些循环访问同样的数组,对相同的数据作不同的运算。这样能使得读入 Cache 的数据在被替换出去之前,能得到反复的使为
//修改前
for (j = 0; j < 100; j = j + 1)
x[i][j] = a[i][j] + b[i][j];
for (j = 0; j < 100; j = j + 1)
y[i][j] = a[i][j] - b[i][j];
//修改后
for (j = 0; j < 100; j = j + 1) {
x[i][j] = a[i][j] + b[i][j];
y[i][j] = a[i][j] - b[i][j];
}
4、分块
**基本思想:**把对数组的整行或整列访问改为按块进行,尽量集中访问,减少替换,提高访问的局部性。
// 矩阵乘法
// 改进前
for (i = 0; i < N; i = i + 1)
for (j = 0; j < N; j = j + 1) {
r = 0;
for (k = 0; k < N; k = k + 1)
r = r + y[i][k] * z[k][j];
x[i][j] = r;
}
// 改进后
for (jj = 0; jj < N; jj = jj + B)
for (kk = 0; kk < N; kk = kk + B)
for (i = 0; i < N; i = i + 1)
for (j = jj; j < min(jj + B - 1, N); j = j + 1) {
r = 0;
for (k = kk; k < min(kk + B - 1, N); k = k + 1)
r = r + y[i][k] * z[k][j];
x[i][j] = x[i][j] + r;
}
在分块前,可能出现z的上半被拿进cache,然后遍历到当前枚举列的下半部分时,上半在cache中的部分被下半替换掉,然后遍历下一列的时候仍然命中失败
分块后:我们按块遍历,使得每块除了冷启动缺失外剩下的都能命中。
四、降低不命中开销
4.1 让读不命中优先于写
Cache中的写缓冲器导致对存储器访问的复杂化
- 在读不命中时,所读单元的最新值有可能还在写缓冲器中,尚未写入主存。
SW R3, 512 (R0);
LW R1, 1024(R0);
LW R2, 512(R0);
//三者Cache索引相同
//导致第三条指令时,数据仍在写缓冲器里,读失效
解决问题的方法(读不命中的处理)
- 推迟对读不命中的处理,直到写缓冲器清空
缺点:(读不命中的开销增加) - 检查写缓冲器中的内容,若无相同,且存储器可用,继续处理读不命中(常用方案)
在写回法Cache中,也可采用写缓冲器
4.2 写缓冲合并
- 提高写缓冲器的效率
- 写直达 Cache 依靠写缓冲来减少对下一级存储器写操作的时间.。
- 如果写缓冲器为空,就把数据和相应地址写入该缓冲器。从CPU的角度来看,该写操作就算是完成了。
- 如果写缓冲器中已经有了待写入的数据,就要把这次的写入地址与写缓冲器中已有的所有地址进行比较,看是否有匹配的项。如果有地址匹配而对应的位置又是空闲的,就把这次要写入的数据与该项合并。这就叫写缓冲合并。
- 如果写缓冲器满且又没有能进行写合并的项,就必须等待。
- 作用
- 连续写入多个字的速度快于每次只写入一个字的操作;
- 提高了写缓冲器的空间利用率,减少因写缓冲器满而要进行的等待时间。
下面给出采用和不采用写合并的例子。这里假设写缓冲器有4项,每项能够存放4个64位的字。图中V代表有效位。当不采用写合并时,写入4个连续存放的数据,就会使写缓冲器满了,3/4的空间被浪费。而当采用写合并时,则只需占用一个项。
我们每写入一个字,发现和写缓冲中的某一项中的某个字地址连续,并且对应位置空闲我们就合并
4.3 请求字处理技术
1、请求字
从下一级存储器调入Cache的块中,只有一个字是立即需要的。这个字称为请求字
基本思想:应尽早把请求字发送给CPU
- **尽早重启动:**调块时,从块的起始位置开始读起,一旦请求字到达,就立即发送给CPU,让CPU继续
执行。 - **请求字优先:**调块时,从请求字所在的位置读起这样,第一个读出的字便是请求字。将之立即发送给CPU。
这种技术在以下情况下效果不大:
- 如果块大小本身就很小的化,该技术的效果不是很明显
- 下一条指令正好访问同一块的另一部分
4.4 非阻塞Cache技术
采用尽早重启动技术时,在请求字到达之前,CPU还是要处于等待状态。
非阻塞Cache:采用记分牌或者 Tomasulo类控制方式,允许指令乱序执行, CPU 无需在 Cache 不命中时停顿;
进一步提高性能:
- 允许不命中下命中,Cache不命中时 仍允许CPU进行其他命中的访问
- 想进一步提高性能,让Cache允许多个不命中重叠。即支持“多重不命中下命中”
可以同时处理的不命中次数越多,所能带来的性能上的提高就越大。但这并不意味着不命中次数越多越好。
有人进行数据Cache和阻塞Cache平均存储器等待时间比值的模拟研究:
- 测试条件:8K 直接映象 Cache,块大小为 32 字节
- 测试程序:SPEC92(14个浮点程序,4个整数程序)
结果表明
在重叠不命中个数为1、2和 64的情况下
浮点程序的平均比值分别为:76%、51%和39%
整数程序的平均比值则分别为:81%、78%和78%
对于整数程序来说,重看次数对性能提高影响不大,简单的“一次不命中下命中”就几乎可以得到所有的好处
非阻塞Cache的潜在优点:
组相联降低不命中率的同时增大了命中开销,而非阻塞Cache可以达到组相联类似的效果而不增加命中时间
4.5 采用两级Cache
应把Cache做得更快?还是更大?
答案:二者兼顾,再增加一级Cache
- 第一级Cache(L1)小而快
- 第二级Cache(L2)容量大
性能分析:
$$
\begin{align}
& 平均访存时间 = 命中时间_{L1} + 失效率_{L1} \times 失效开销_{L1} \
& 失效开销_{L1} = 命中时间_{L2} + 失效率_{L2} \times 失效开销_{L2} \
& 平均访存时间 = 命中时间_{L1} + 失效率_{L1} \times (命中时间_{L2} + 失效率_{L2} \times 失效开销_{L2}) \
\end{align}
$$
局部失效率和全局失效率:
局部失效率 = 该级Cache的失效次数 / 到达该级Cache的访问次数
比如上面的 失效 率 L 2 失效率_{L2} 失效率L2
全局失效率 = 该级Cache的失效次数 / CPU发出的访存的总次数
全局失效 率 L 2 = 失效 率 L 1 × 失效 率 L 2 全局失效率_{L2} = 失效率_{L1} \times 失效率_{L2} 全局失效率L2=失效率L1×失效率L2
评价第二级Cache时,应使用全局失效率这个指标。它指出了在CPU发出的访存中,究竟有多大比例是穿过各级Cache,最终到达存储器的。
采用两级 Cache 时,每条指令的平均访存停顿时间:
每条指令的平均访存停顿时间
=
每条指令的平均不命中次
数
L
1
×
命中时
间
L
2
+
每条指令的平均不命中次
数
L
2
×
不命中开
销
L
2
每条指令的平均访存停顿时间 = 每条指令的平均不命中次数_{L1} \times 命中时间_{L2} + 每条指令的平均不命中次数_{L2} \times 不命中开销_{L2}
每条指令的平均访存停顿时间=每条指令的平均不命中次数L1×命中时间L2+每条指令的平均不命中次数L2×不命中开销L2
例:考虑某一 两级Cache:第一级 Cache 为 L1,第二级 Cache 为 L2。
(1) 假设在 1000 次访存中, L1 的不命中是 40 次, L2 的不命中是 20 次。求各种局部不命中率和全局不命中率。
(2) 假设 L2 的命中时间是 10 个时钟周期,L2 的不命中开销是 100 时钟周期,L1的命中时间是 1 个时钟周期,平均每条指令访存 1.5 次,不考虑写操作的影响。问:平均访存时间是多少?每条指令的平均停顿时间是多少个时钟周期?解:
$$
\begin{align}
& (1) \
& 局部不命中率_{L1} = \frac{40}{1000} = 4%, 局部不命中率_{L2} = \frac{20}{40} = 50% \
& 全局不命中率_{L1} = \frac{40}{1000} = 4%, 全局不命中率_{L2} = \frac{20}{1000} = 2% \
& (2) \
& 平均访存时间 = 命中时间_{L1} + 失效率_{L1} \times (命中时间_{L2} + 失效率_{L2}\times 失效开销_{L2}) \
& = 1 + 4%\times(10 + 50% \times 100) \
& = 3.4个时钟周期\
& 平均停顿时间 = (3.4 - 1.0) \times 1.5 = 3.6\end{align}
$$
对于第二级Cache,我们有以下结论:
- 在第二级 Cache 比第一级 Cache大得多的情况下,两级 Cache 的全局失效率和容量与第二级 Cache 相同的单级 Cache 的失效率非常接近
- 局部失效率不是衡量第二级 Cache 的一个好指标,因此,在评价第二级 Cache 时,应用全局失效率这个指标
第二级Cache不会影响CPU的时钟频率,因此其设计有更大的考虑空间
两个问题:
- 它能否降低CPI中的平均访存时间部分?
- 它的成本是多少?
第二级Cache的参数:
-
容量
第二级Cache的容量一般比第一级的大许多。如512KB,1024KB。
-
相联度
第二级Cache可采用较高的相联度或伪相联方法。
例:给出有关第二级Cache的以下数据:
(1)2路组相联使命中时间增加10% × CPU时钟周期
(2)对于直接映象, 命中时 间 L 2 命中时间_{L2} 命中时间L2 = 10个时钟周期
(3)对于直接映象, 局部失效 率 L 2 局部失效率_{L2} 局部失效率L2 = 25%
(4)对于2路组相联, 局部失效 率 L 2 局部失效率_{L2} 局部失效率L2 = 20%
(5) 失效开 销 L 2 失效开销_{L2} 失效开销L2 = 50个时钟周期
试问第二级Cache的相联度对失效开销的影响如何?解:
失效开 销 直接映像 , L 1 = 10 + 25 % × 50 = 22.5 失效开 销 2 路组相联 , L 1 = 10.1 + 20 % × 50 = 20.1 把第二级 C a c h e 的命中时间取整 , 得 10 或 11 , 则 : 失效开 销 直接映像 , L 1 = 10 + 20 % × 50 = 20 失效开 销 2 路组相联 , L 1 = 11 + 20 % × 50 = 21 故对于第二级 C a c h e 来说, 2 路组相联优于直接映象 \begin{align} & 失效开销_{直接映像, L1} = 10 + 25\% \times 50 = 22.5\\ & 失效开销_{2路组相联, L1} = 10.1 + 20\% \times 50 = 20.1\\ & 把第二级Cache的命中时间取整,得10或11,则: \\ & 失效开销_{直接映像, L1} = 10 + 20\% \times 50 = 20\\ & 失效开销_{2路组相联, L1} = 11 + 20\% \times 50 = 21\\ & 故对于第二级Cache来说,2路组相联优于直接映象 \end{align} 失效开销直接映像,L1=10+25%×50=22.5失效开销2路组相联,L1=10.1+20%×50=20.1把第二级Cache的命中时间取整,得10或11,则:失效开销直接映像,L1=10+20%×50=20失效开销2路组相联,L1=11+20%×50=21故对于第二级Cache来说,2路组相联优于直接映象
- 块大小
- 第二级 Cache 可采用较大的块,如64、128、256 字节
- 为减少平均访存时间,可以让容量较小的第一级 Cache 采用较小的块,而让容量较大的第二级 Cache 采用较大的块。
- 多级包容性
需要考虑的另一个问题:
第一级 cache 中的数据是否总是同时存在于第二级 Cache 中。如果是就说第二级 Cache 是具有多级包容性的。
五、减少命中时间
命中时间直接影响到处理器的时钟频率。在当今的许多计算机中,往往是Cache的访问时间限制了处理器的时钟频率。
5.1 容量小、结构简单的Cache
为了有效地减少Cache的命中时间,可以采用容量小、结构简单的Cache。
- 硬件越简单,速度就越快。
- 应使 Cache 足够小,以便可以与 CPU 一起放在同一块芯片上。避免因片外访问而增加时间开销
- 有些设计采用了一种折中方案:把Cache的标识放在片内,而把Cache的数据存储体放在片外,这样既可以实现快速标识检测,又能利用独立的存储芯片来提供更大的容量。
- 采用结构简单的 Cache,比如直接映像 Cache 。
5.2 虚拟Cache
按照访问Cache的地址是物理地址还是虚拟地址,可把Cache分为物理Cache 和虚拟Cache。
物理Cache
- 使用物理地址进行访问的传统 Cache
- 标识存储器中存放的是物理地址,进行地址检测也是用物理地址。
- 缺点:地址转换和访问 Cache 串行进行,访问速度很慢
虚拟Cache
- 可以直接用虚拟地址进行访问的 Cache 。标识存储器中存放的是虚拟地址,进行地址检测用的也是虚拟地址。
- 优点:在命中时不需要地址转换,省去了地址转换的时间。即使不命中,地址转换和访问 Cache 也是并行进行的,其速度比物理 Cache快很多。
然而,并非所有计算机都采用虚拟 Cache
- 由于新进程的虚拟地址可能与原进程相同,所以每当进行进程切换时需要清空 Cache。
- 解决方法:在地址标识中增加 PID(进程标识符)字段
- 然而操作系统为了减少PID位数,PID 可能循环使用,所以也可能存在多个进程使用同一个 PID ,所以此时也需要清空 cache
虚拟索引-物理标识方法
原理:使用虚地址中的页内位移(物理/虚拟地址的页位移不变)生成 Cache 索引,虚实转换后的实页地址作为标志 tag
优点:兼得虚拟 cache 和物理 cache 的好处
局限性: Cache 容量受到限制
Cache 容量 ≤ 页大小 × 相联度
举例:IBM 3033 的 Cache:页大小=4KB 相联度=16
Cache容量=16 × 4KB=64KB
5.3 Cache 访问流水化
- 对第一级Cache的访问按流水方式组织
- 访问Cache需要多个时钟周期才可以完成,比如:
- Pentium访问指令Cache需要一个时钟周期
- Pentium Pro到Pentium Ⅲ需要两个时钟周期
- Pentium 4 则需要4个时钟周期
实际上这种方法并不能真正减少Cache的命中时间,但可以提高访问Cache的带宽。
5.4 踪迹 Cache
-
开发指令级并行性所遇到的一个挑战是:
- 当要每个时钟周期流出超过4条指令时,要提供足够多条彼此互不相关的指令是很困难的。一个解决方法:采用踪迹 Cache
-
**一个解决方法:**采用踪迹 Cache
-
存放CPU所执行的动态指令序列
包含了由分支预测展开的指令,该分支预测是否正确需要在取到该指令时进行确认。
-
**优点:**能够提高指令Cache的空间利用率。
缺点:
- 地址映象机制复杂。
- 相同的指令序列有可能被当作条件分支的不同选择而重复存放。
六、Cache 优化技术总结
- ‘+’ 号:表示改进了相应指标。
- ‘-’ 号:表示它使该指标变差。
- 空格栏:表示它对该指标无影响,
- 复杂性:0 表示最容易,3表示最复杂。
七、复习题
1、请分析影响平均访存时间的因素有哪些?针对每个因素,各举出1种改进方法
- 不命中率
- 增加Cache绒布管理
- 不命中开销
- 写缓冲合并技术
- 命中时间
- 虚拟Cache
2、请简述Cache的3C失效
- 强制性不命中(冷启动缺失)
- 第一次访问一个块时,该块不在Cache中,需从下级存储器中调入Cache,这就是强制性不命中
- 容量缺失
- 如果程序执行时所需的块不能全部调入Cache中,则当某些块被替换后,若又重新被访问,就会发生不命中。这种不命中称为容量不命中
- 冲突碰撞缺失
- 在组相联或直接映像Cache中,若太多的块映像到同一组(块)中,则会出现该组中某个块被别的块替换,然后又被重新访问的情况
3、请针对3C失效的每种失效给出一种降低失效率的方法,并分别分析该种方法的缺点
- 强制性不命中——增加Cache块容量
- 块数变少,增加了冲突碰撞缺失
- 容量缺失——增加Cache容量
- 增大了成本和命中开销
- 冲突碰撞缺失——提高相联度
- 增大了命中开销
4、请简述伪相联的基本思想
在逻辑上把直接映像Cache的空间上下平分为两个区。对于任何一次访问,伪相联Cache先按直接映像Cache的方式去处理。若命中,则其访问过程与直接映像Cache的情况一样。若不命中,则再到另区相应的位置去查找。若找到,则发生了伪命中,否则就只好访问下级存储器。
5、请例举一种编译器优化的方法,并举例说明
如,数组合并:将本来相互独立的多个数组合并成为一个复合数组,以提高访问它们的局部性。