开始的计算机系统中存储器层次包括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博客
简书博客
知乎专栏