存储之Cache


Cache存在的意义:根据局部性原理,对于当前访问的存储位置,接下来,它很可能会被多次访问(时间局部性),它的相邻位置也可能会被访问(空间局部性)。由于访问内存的速度远大于访问寄存器,所以在二者之间设置cache,来暂存一部分指令或数据。

内存:假设每个内存地址有\(m\)位,那么共有\(M=2^m\)个不同的地址,即内存容量为\(M\)字节。如下图所示,\(m\)位地址划分为\(t\)标记\(s\)组索引\(b\)块偏移,用于描述该地址所存数据(大小为byte)的副本在cache中的位置。

Cache:使用元组\((S,E,B,m)\)来描述,具体为:

  • \(S=2^s\)表示Cache共有\(S\)个高速缓存组(cache set)
  • \(E\)表示每组均有\(E\)个高速缓存行(cache line)
  • \(B=2^b\)表示每行均包含大小为\(B\)个字节的高速缓存块(block)
  • 总容量为\(C=S*E*B\)个字节

特别注意:每个cache line包含两部分,有效位/标记位+高速缓存块;通常说line size是指高速缓存块的大小,即\(B\)
1

假设指令指示CPU从地址A读取一个数据,CPU将地址A发送到Cache,如果Cache含有该数据的副本,它将立即返回数据给CPU;那么Cache如何找到该数据的呢?

  • 组选择,Cache根据地址A中的s位组索引定位到组
  • 行匹配,Cache根据地址A中的t位标记位定位到行(注意:当且仅当该行设置了有效位,并且该行的t位标记位与地址A的t位标记位完全相同时,该行才包含所需数据副本)
  • 取数据,Cache根据地址A中的b位块偏移定位到该数据在block中的位置

Cache与内存的映射关系

首先要明确,cache有多个组,每个组有一个或多个行;内存有多个块,每个块的大小为\(B\)(即line size),块的数量为\(2^m/B\)映射关系是内存块与cache line的映射

一、直接映射高速缓存

\(E=1\),即cache中每组只有一行(即查取数据时没有行匹配过程)。比如\((S,E,B,m)=(4,1,2,4)\),即cache有4组/4行,内存有8块,则映射关系如下:
2

二、全相联高速缓存

\(S=1\),即只有一个组;所以,查取数据时没有组选择过程,定位数据副本过程如下:
4

三、组相联高速缓存

Cache包含多个组,每组又有多个行,所以查取数据时组选择、行匹配都不会少,过程如下:
5
6

理解关键:以上就是CSAPP对cache的主要描述,此时我们对cache的实现原理已经比较清楚了,但是有个很关键且令人困惑的问题:一直在说查取数据,cache与内存的三种映射关系并没有体现出来!我们在各种资料上常见的三张映射关系图,如何与上述知识对接呢?关键就在于t位的标记位! 首先,\(t=m-s-b\),它的含义是每行(或者说每个cache line)可以与\(2^t=\frac{M}{S\cdot B}\)种不同的内存块产生映射关系(注意,是种,不是个)。

  • 全相联映射中,每个cache line可以与\(M/B\)(因为S=1)种内存块(即所有内存块!)产生映射
  • 直接映射和组相联映射中,每个cache line可以与\(M/SB\)种内存块产生映射。区别就在于前者的E=1,也就是说每组只有一行,即内存块与cache line的映射等价于与组的映射。对于后者,所谓的n路组相联,也就是说每组包含n个cache line,即内存块先映射到一个组,然后与该组中的任意一个cache line产生映射。简单描述就是:直接映射是内存块直接与cache line映射;组相联映射是内存块先映射到组,再映射到cache line!
  • 假设cache中组的编号为0,1,2,...,S-1,对于直接映射和组相连映射,内存地址address映射到组编号为:\(address\ mod\ S\)

缓存缺失的分类

缓存不命中时,常用的替换策略有:最不常使用(LFU)策略,最近最少使用(LRU)策略等。

  • 强制缺失:CPU第一次访问时的不命中,数据全在内存中,还未缓存
  • 容量缺失:某个cacheline被替换时,其他组(set)全满,再次访问该cacheline时发生缺失,即cache不能存放程序运行所需的所有块
  • 冲突缺失:某个cacheline被替换时,其他组(set)仍有空的cacheline,再次访问该cacheline时发生缺失,即映射到同一组中的数据块个数超过组内可容纳块数时,竞争所导致的缺失
  • 一致性缺失:真假共享所导致的缺失,两个核共享同一个数据为真共享,两个核共享的数据在同一个cacheline中为假共享

全相联映射中,除了第一次的强制缺失,其他全是容量缺失(因为只有一个组)。直接相联映射和组相联映射中,缺失必定发生在某个组(set):如果其他组全满,则为容量缺失;反之,为冲突缺失。

有关写的问题

问题1:假设要写一个已经缓存的字w(写命中,write hit),那么在cache中更新副本后,如何更新低一层的副本呢?

  • 方法1:直写(write through),更新cache中对应的数据副本,并直接将块写入存储器。
  • 方法2:写回(write back),仅更新cache中对应的数据副本,当所在块将要被替换时,才写入存储器。

方法2相比方法1减少了总线流量,但是增加了复杂性,cache必须为每个cache line维护一个额外的修改位(dirty bit),表明这个高速缓存块被修改过。此外,在多级cache系统中,存在缓存一致性的问题。
这两种策略都使用了一种写缓冲区(write buffer),将数据放入这个缓冲区后,马上就可以进行缓存操作,而不需要等待将数据写入存储器的全部延迟时间。

问题2:如何处理写不命中?

  • 方法1:写分配(write-allocate),加载低一层中相应的块到cache中,然后更新cache。
  • 方法2:非写分配(non-write-allocate),避开cahce,直接写到低一层中。

方法1的缺点是每次不命中都会导致一个块从低一层移动到cache中;方法2的缺点是非常耗时。直写通常是非写分配的,写回通常是写分配的。
由于cpu中cache对程序员是透明的,建议默认使用写回+写分配的模型。原因:一是直写太耗时;二是与读相对应。

cache优化

命中率(hit rate):命中的数量/内存访问的数量
缺失率(miss rate):1-命中率
命中时间(hit time):从高速缓存传送一个字到CPU所需的时间,包括组选择、行确认和字选择的时间。
缺失代价(miss penalty):缺失所需要的额外的时间。

\(存储器平均访问时间=命中时间+缺失率\times 缺失代价\)

六种基本缓存优化的方法

其中:6降低命中时间、1-3降低缺失率、4-5降低缺失代价:

  1. 增大块大小,利用了空间局部性,可以减少强制缺失。但是增加了不命中的处罚。
  2. 增大cache大小,可以减少容量缺失,但是会增加命中时的查找时间。
  3. 提高相联程度,可以减少冲突缺失,但是也会增加命中时的查找时间。
  4. 采用多级缓存,可以降低缺失代价。\(平均存储器访问时间=命中时间_{L1}+缺失率_{L1}\times (命中时间_{L2}+缺失率_{L2}\times 缺失代价_{L2})\)
  5. 为读取缺失指定高于写入操作的优先级,降低缺失代价。
  6. 在缓存索引期间避免地址转换,使用页偏移地址(虚拟地址与物理地址相同的部分)来索引,减少命中时间。

十种高级优化方法

除了命中时间、缺失率和缺失代价这三个指标,引入缓存带宽功耗两个度量。

  1. 降低命中时间:小而简单的L1 cache、路预测(通常可降低功耗)
  2. 增加缓存带宽:流水化缓存、多组缓存、无阻塞缓存(对功耗具有不确定性影响)
  3. 降低缺失率:编译器优化(缩短编译时间可降低功耗)
  4. 降低缺失代价:关键字优化、合并写缓冲区(对功耗影响很小)
  5. 通过并行降低缺失率或缺失代价:硬件预取、编译器预取(通常会增加功耗)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM