Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

缓存基础

复习

  1. 第三十章:了解了存储器按地址存取
  2. 第三十一章:知道了存储层次结构,也知道了每一层都是下层的缓存

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、随机

参考资料

  1. Wikipedia(zh):CPU缓存:缓存的基本概念
  2. Wikipedia(zh):缓存替换策略:LRU 等替换算法
  3. 《深入理解计算机系统》第 6 章:存储器层次结构

思考题答案(仅供参考)

  帮不上忙。程序没有空间局部性也没有时间局部性,缓存搬回来的那一整块里,绝大部分字节之后根本不会被用到,白白浪费了搬运的开销,命中率也很低。这就是所谓的“缓存不友好“——它提醒我们,数据的组织方式和访问顺序,会实实在在影响程序快慢。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪