快存储与大存储怎样合作
复习
- CPU 会不断从寄存器和主存读取指令与数据
- 寄存器很快,却不适合做得很大
- 主存能保存更多数据,访问通常更慢
本篇讨论怎样让已经能运行的机器少等一会儿。跳过它,不影响后面连接总线与外设。
TL;DR
- 数据会按“少而快”到“多而慢”分层存放
- 缓存把近期可能再用的数据留在运算器附近
- 找到叫命中,没找到叫缺失
正文
如果所有数据都放在寄存器里,机器会快得开心,也贵得惊人;如果每次计算都去大容量设备取数,ALU 大部分时间只能干等。
既然一种存储解决不了所有问题,就把几种存储叠起来:
寄存器 最快、最小
缓存
主存
固态硬盘等 最慢、最大
眼前就要用的数放在上面,暂时不用的放在下面。这就是存储层次结构。
为什么分层有用
程序访问数据并不平均。刚用过的数据很可能马上再用,刚访问某个地址也常常会继续访问附近地址。前者叫时间局部性,后者叫空间局部性。
累加一串连续数字时,机器会依次读取相邻地址;循环又会反复使用同一小段指令。只要把这些“最近、附近”的内容留在快速层,多数访问就不必跑到更慢的层级。
在书桌上留几本常用书
缓存像书桌,主存像书架。机器读取某个地址时,先查缓存:
- 找到了:命中,直接交出数据
- 没找到:缺失,去主存取回,并把附近一小块数据一起放进缓存
缓存装满后,新数据要进来,就得让一块旧数据腾位置。LRU 的想法是优先替换最长时间没有使用的数据;真实硬件也常采用更容易实现的近似方案。
修改缓存数据后,可以立即同步到主存,也可以等它将来被替换时再写回。后者减少写入次数,却要额外记住哪些缓存块已经修改。
补充:两张书桌上的同一本书
我们的极简机器只有一个运算核心,不会遇到缓存一致性问题。若未来有两个核心,各自缓存了地址 100 的值 5,而核心 A 把它改成 6,核心 B 手里的副本就过期了。
多核处理器会使用缓存一致性协议协调这些副本。常见做法是:某个核心修改前先通知其他缓存,让同一地址的旧副本失效。MESI 是实现这类规则的一种协议。
这里不展开状态细节。只留下一个印象:副本换来速度,也带来同步副本的责任。 对当前单核机器而言,知道缓存为什么存在已经足够。
缓存优化的是“取数据要等多久”。下一篇补充再看另一种性能问题:一条指令要分成许多阶段,能不能让不同指令的阶段重叠进行?不关心性能细节的读者可以直接进入总线。
思考题
遍历连续数组,为什么通常比随机读取同样多的地址更容易命中缓存?
小结
知识点
- 存储层次在速度、容量与成本之间折中
- 时间局部性和空间局部性让缓存有效
- 缓存命中、缺失、替换与写回
- 多核缓存中的副本需要保持一致
思考题答案(仅供参考)
连续访问具有空间局部性。缓存取回一个地址时通常会把附近数据一起带上来,后续访问便可能直接命中;随机地址更容易反复去主存寻找。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪