Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

快存储与大存储怎样合作

复习

  • CPU 会不断从寄存器和主存读取指令与数据
  • 寄存器很快,却不适合做得很大
  • 主存能保存更多数据,访问通常更慢

本篇讨论怎样让已经能运行的机器少等一会儿。跳过它,不影响后面连接总线与外设。

TL;DR

  • 数据会按“少而快”到“多而慢”分层存放
  • 缓存把近期可能再用的数据留在运算器附近
  • 找到叫命中,没找到叫缺失

正文

  如果所有数据都放在寄存器里,机器会快得开心,也贵得惊人;如果每次计算都去大容量设备取数,ALU 大部分时间只能干等。

  既然一种存储解决不了所有问题,就把几种存储叠起来:

寄存器        最快、最小
缓存
主存
固态硬盘等    最慢、最大

  眼前就要用的数放在上面,暂时不用的放在下面。这就是存储层次结构

为什么分层有用

  程序访问数据并不平均。刚用过的数据很可能马上再用,刚访问某个地址也常常会继续访问附近地址。前者叫时间局部性,后者叫空间局部性。

  累加一串连续数字时,机器会依次读取相邻地址;循环又会反复使用同一小段指令。只要把这些“最近、附近”的内容留在快速层,多数访问就不必跑到更慢的层级。

在书桌上留几本常用书

  缓存像书桌,主存像书架。机器读取某个地址时,先查缓存:

  • 找到了:命中,直接交出数据
  • 没找到:缺失,去主存取回,并把附近一小块数据一起放进缓存

  缓存装满后,新数据要进来,就得让一块旧数据腾位置。LRU 的想法是优先替换最长时间没有使用的数据;真实硬件也常采用更容易实现的近似方案。

  修改缓存数据后,可以立即同步到主存,也可以等它将来被替换时再写回。后者减少写入次数,却要额外记住哪些缓存块已经修改。

补充:两张书桌上的同一本书

  我们的极简机器只有一个运算核心,不会遇到缓存一致性问题。若未来有两个核心,各自缓存了地址 100 的值 5,而核心 A 把它改成 6,核心 B 手里的副本就过期了。

  多核处理器会使用缓存一致性协议协调这些副本。常见做法是:某个核心修改前先通知其他缓存,让同一地址的旧副本失效。MESI 是实现这类规则的一种协议。

  这里不展开状态细节。只留下一个印象:副本换来速度,也带来同步副本的责任。 对当前单核机器而言,知道缓存为什么存在已经足够。

  缓存优化的是“取数据要等多久”。下一篇补充再看另一种性能问题:一条指令要分成许多阶段,能不能让不同指令的阶段重叠进行?不关心性能细节的读者可以直接进入总线。

思考题

  遍历连续数组,为什么通常比随机读取同样多的地址更容易命中缓存?

小结

知识点

  • 存储层次在速度、容量与成本之间折中
  • 时间局部性和空间局部性让缓存有效
  • 缓存命中、缺失、替换与写回
  • 多核缓存中的副本需要保持一致

思考题答案(仅供参考)

  连续访问具有空间局部性。缓存取回一个地址时通常会把附近数据一起带上来,后续访问便可能直接命中;随机地址更容易反复去主存寻找。

协议

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

封面图

设计师 | 南国微雪