开始的计算机系统中存储器层次包括CPU寄存器、主存(DRAM)和硬盘,后来为了缓解寄存器与主存间速度的差异,系统设计者在它们之间增加了高速缓存(SRAM),它的访问速度几乎可以和寄存器一样快。
随着CPU和主存的性能差距逐渐拉大,后来在原高速缓存(L1高速缓存)和内存之间有增加了L2和L3级高速缓存。一般访问寄存器需要1个时钟周期,访问L1级高速缓存需要4个时钟周期,访问L2级高速缓存需要10个时钟周期,访问L3级高速缓存需要50个时钟周期。
Intel Core i7的高速缓存层次结构如下图所示:
下文的介绍以简单的L1级高速缓存来介绍。
1.通用高速缓存存储器组织结构
假设系统的存储器地址共有m位,形成M = 2m 个不同的地址。该机器的高速缓存被组织成一个有S = 2m个高速缓存组,每组包含E个高速缓存行,每行包含B = 2b个字节组成。
参数 | 描述 |
---|---|
S | 组数 |
E | 每组的行数 |
B | 块的大小(字节) |
m | 物理地址位数 |
高速缓存的大小可以用C=S * E * B来表示。
2.直接映射高速缓存
当E=1,也即每组只有一行时,我们称之为直接映射(direct-mapped cache)。
2.1组的选择
高速缓存从地址中间抽取出s位组成高速缓存的组索引。
2.2行匹配和字选择
上一步已经找到了高速缓存组,现在要先确定你要找的数据是否已经被缓存了,这由高速缓存结构中的一个有效位来决定。若为1则有效,继续比对标记字段,反之为0就无效,直接判断缓存不命中。
接下来再由高速缓存检查从该地址高t位(标记字段)是否和高速缓存中的标记段一致。若一致则缓存命中,否则缓存不命中。
- 若行已经匹配,我们就知道所需要的字已经在这个块中的某个地方了。我们把块看成是一个字节数组的话,那么地址低位的b位块偏移字段就是所需字的索引值。
2.3 抖动冲突不命中
当程序访问大小为2的幂的数组时,直接映射高速缓存中通常会发生冲突不命中。考虑一个计算两个向量点积的函数:
float dotprod(float x[8], float y[8])
{
float sum = 0.0;
int i;
for(i=0; i<8; i++){
sum += x[i] * y[i];
}
return sum;
}
假设浮点数占4个字节,x被加载到地址0开始的32连续内存中,y被加载到紧随其后的32连续字节。为了说明问题,假设一个块是16个字节,高速缓存由2个组组成。sum实际上会被存放在寄存器中,然后完成第一次循环时,为了取得x[0]的值,x首先被全部加入到高速缓存并占满整个高速缓存;然后为了取得y[0]的值,x会被换出,y又被全部加入到高速缓存并占满整个高速缓存;这样来回往复,形成抖动(thrash),造成速度下降2到3倍。
所以为了避免发生以上的抖动问题,在定义数组大小时尽量不要取2的幂大小。例如在上例中,我们在x数组的末尾添加4字节的空间。这样y数组的地址就不会和x一样映射到同一个高速缓存组了。
上图解释了为什么选择地址的中间位来作为高速缓存的索引。
3.组相联高速缓存
它的组选择与直接映射的一样,而它的行匹配要比直接映射复杂,因为他要检查多个行的标记字段和有效位,以确定所请求的字是否在集合中。
其中,组里的任何一行都可以包含任何映射到该组的内存块。
4.全相联高速缓存
全相联的结构只包含一个组,里面包含了所有的缓存行。
而它的行匹配和字选择也是和组相联是一样的。因为高速缓存电路必须并行的搜索许多相匹配的标记,构造一个很大很快的全相联高速缓存是很昂贵的,因此它只适合做小的高速缓存。例如Linux系统中的快表TLB就是全相联的,它是用来缓存页表项的,用于快速地址翻译。
获取更多知识,请点击关注:
嵌入式Linux&ARM
CSDN博客
简书博客
知乎专栏