缓存基础
复习
- 第三十章:了解了存储器按地址存取
- 第三十一章:知道了存储层次结构,也知道了每一层都是下层的缓存
TL;DR
- 缓存是介于 CPU 和内存之间的一块小而快的存储
- CPU 先查缓存,命中就直接用,缺失才去内存取
- 数据按“块“搬运,一次搬一整块,利用空间局部性
- 缓存满了要用替换策略决定赶走谁,常用 LRU
正文
缓存是什么
第三十一章说“每一层都是下层的缓存“。现在我们把目光聚焦到最典型的那一对:CPU 和内存之间的高速缓存(Cache)。
CPU 快,内存慢,两者速度差了几十上百倍。如果 CPU 每要一个数据都去内存拿,就得干等。缓存的作用就是:把最近用到的数据放在 CPU 旁边的一小块快速存储里,让 CPU 大部分时候不用走远路。
命中与缺失
缓存的工作逻辑非常朴素:
- CPU 要数据时,先看缓存里有没有
- 有,叫命中(Hit),直接拿走,很快
- 没有,叫缺失(Miss),只好去内存取
衡量缓存好不好,就看命中率(Hit Rate)——命中的次数占总访问次数的比例。因为内存相对缓存太慢,命中率哪怕只提高几个百分点,整体性能都会有明显提升。
按“块“搬运
缺失时,是不是只把需要的那个数据搬回来?不是的。
缓存一次搬一块(Block),也叫一个缓存行(Cache Line),比如一次搬 64 字节。因为根据空间局部性,CPU 接下来多半会用到旁边的数据,一次搬一整块,后面的访问就很可能直接命中了。
这就像搬家:明知道接下来还要用书房里的书,与其一本一本跑回去拿,不如一次抱一摞回来。
数据怎么放进缓存
内存里的块,要放到缓存的哪个位置?常见有两种方案:
- 直接映射:每个内存块只能放到缓存里固定的一个位置。简单、快,但容易“撞车“
- 组相联映射:每个内存块可以放到某一组里的任意一路。灵活一些,冲突少一些,代价是判断“在不在“时要多比几次
可以类比快递柜:直接映射就像“每栋楼的快递只能放对应编号的柜子“,组相联则像“可以放同一排任意一个空柜“。后者更不容易被占满,但找起来稍麻烦。
缓存满了怎么办
缓存总有满的时候。这时需要替换策略决定赶走哪一块,给新数据腾地方:
- 最近最少使用(LRU):优先淘汰最久没被访问的,最贴合时间局部性,效果通常最好
- 先进先出(FIFO):谁先进来谁先被淘汰,简单但不一定合理
- 随机替换:随便挑一个,实现最简单
思考题
假设缓存一次搬 64 字节,而你写的程序每次只用一个字节、然后跳到很远的、几乎不重访的位置。这样的程序,缓存能帮上忙吗?为什么?
小结
知识点
- 缓存的作用:在 CPU 和内存之间做缓冲
- 命中与缺失、命中率
- 按块(缓存行)搬运,利用空间局部性
- 直接映射与组相联映射
- 替换策略:LRU、FIFO、随机
参考资料
- Wikipedia(zh):CPU缓存:缓存的基本概念
- Wikipedia(zh):缓存替换策略:LRU 等替换算法
- 《深入理解计算机系统》第 6 章:存储器层次结构
思考题答案(仅供参考)
帮不上忙。程序没有空间局部性也没有时间局部性,缓存搬回来的那一整块里,绝大部分字节之后根本不会被用到,白白浪费了搬运的开销,命中率也很低。这就是所谓的“缓存不友好“——它提醒我们,数据的组织方式和访问顺序,会实实在在影响程序快慢。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪