Cache 写策略:写直达、写回与替换算法
上一篇讲了 Cache 映射策略,解决的是“主存块能放到哪里”的问题。但 Cache 真正运行起来后,还会遇到另外两个非常现实的问题:
当 CPU 要修改数据时,Cache 和主存该怎么保持一致?
当 Cache 放不下新的数据块时,旧块又该淘汰谁?
这就是 Cache 写策略和替换算法要回答的问题。
它们决定了缓存系统不仅能不能“读得快”,还决定了“写得是否高效、数据是否一致、空间是否利用得合理”。
一、为什么 Cache 的写操作比读操作更麻烦
读操作相对直接:
- 命中则直接返回;
- 不命中则从下层取回来。
但写操作会引入额外复杂性,因为此时涉及两个层级的数据状态:
- Cache 里有一份;
- 主存里通常也有一份逻辑上的对应内容。
一旦 CPU 改了值,就必须考虑:
- 主存是否要立刻同步;
- 如果不同步,什么时候同步;
- 不命中时是否先把块拉进 Cache;
- 多级 Cache 或更复杂系统里如何保持一致。
所以写策略本质上是在平衡:
- 写入延迟;
- 总线流量;
- 一致性维护成本;
- 实现复杂度。
二、什么是写直达 write-through
写直达的核心规则很直接:
CPU 对 Cache 的写操作一旦发生,同时也把更新写到下层存储。
也就是说,Cache 和主存会尽量保持同步。
它的优点很明显:
- 数据一致性更直观;
- 主存中的内容比较新;
- 管理逻辑相对清晰。
但代价也不小:
- 每次写命中都要继续写下层;
- 总线和主存写流量会增大;
- 如果程序写得频繁,成本会很可观。
所以写直达的特点可以概括成:
简单、直观,但写压力更大。
三、什么是写回 write-back
写回走的是另一条思路。它的核心规则是:
CPU 修改命中数据时,先只更新 Cache,不立即写回主存,等到该块将来被替换出去时,再把修改后的内容写回下层。
这样做的好处是:
- 多次写操作可以在 Cache 内部吸收;
- 减少对下层的重复写流量;
- 写密集型场景通常更高效。
但它也会引入更多复杂性:
- 必须记录哪些块被修改过;
- 主存一段时间内可能不是最新值;
- 替换时可能要额外触发回写。
所以写回的核心特征是:
性能潜力更好,但管理更复杂。
四、脏位为什么是写回的关键
因为写回策略下,并不是每个 Cache 块都一定被改过。
如果一个块只是被读过,没有被写过,那么它被替换时根本不需要回写主存。为了区分这两种情况,就需要一个标记告诉系统:
- 这个块有没有被修改;
- 替换时需不需要写回。
这就是脏位 dirty bit 的作用。
所以脏位本质上是在记录:
Cache 中这份数据是否已经“比主存更新”。
五、写直达和写回最本质的差异是什么
一句话总结就是:
- 写直达:每次写都同步下层;
- 写回:先只改 Cache,延后统一回写。
对应的工程取舍则是:
- 写直达更简单、更容易保持一致;
- 写回更能减少下层写流量,但需要额外状态管理。
所以这不是谁绝对优谁绝对劣,而是系统设计对复杂度和性能的权衡。
六、写不命中时又会遇到什么问题
除了写命中,写不命中时还得决定:
要不要先把这个块从下层读进 Cache,再执行写操作?
这里常见会有两种思路:
- 写分配
write-allocate - 非写分配
no-write-allocate
七、什么是写分配和非写分配
1. 写分配
写不命中时,先把对应块装入 Cache,再在 Cache 中完成写入。
这通常和写回策略更搭,因为既然以后可能还会继续写这个块,那先拉进 Cache 通常更划算。
2. 非写分配
写不命中时,不把块装入 Cache,而是直接把写操作发往下层存储。
这通常和写直达思路更搭,因为它避免为一次偶发写入占用 Cache 空间。
所以除了“怎么同步下层”,Cache 还要回答“miss 时要不要把它收编进来”。
八、为什么替换算法不可忽略
Cache 容量有限,一旦新块需要进入,而目标位置或目标组已经满了,就必须决定:
现有块里谁应该被淘汰。
这就是替换算法的任务。
如果替换策略不好,可能会把马上还会用到的热点块赶走,导致命中率下降。
所以替换算法并不是附属细节,而是 Cache 空间利用效率的核心因素之一。
九、常见替换算法有哪些直觉
1. LRU Least Recently Used
优先替换最近最久没有被使用的块。
它背后的直觉是:
- 刚用过的块更可能还会再用;
- 很久没用的块留下来的价值可能更低。
这种思路和时间局部性高度契合,所以非常经典。
2. FIFO
谁先进来,谁先被淘汰。
实现相对简单,但未必总能贴合真实访问热点。
3. 随机替换
随机选一个块淘汰。
听起来简单粗暴,但在某些场景下并不一定特别差,而且硬件实现代价较低。
所以替换算法的选择,本质上还是在平衡:
- 命中率效果;
- 维护状态成本;
- 硬件复杂度。
十、替换算法为什么主要出现在组相联和全相联里
因为直接映射根本没有“挑谁淘汰”的自由度。
- 一个块只能去一个固定位置;
- 如果新块来了,原来那一行就只能被它顶掉。
而组相联和全相联由于一个块有多个可放位置,才会出现“组内该踢谁”的决策问题。
所以替换算法和映射策略是紧密耦合的:
- 映射策略决定有没有选择空间;
- 替换算法决定有空间时如何选。
十一、为什么 Cache 写策略和替换策略会影响系统整体性能
因为它们会直接影响:
- 平均访存延迟;
- 总线压力;
- 主存写流量;
- 命中率;
- 高并发访问下的资源竞争。
例如:
- 写回通常能减少大量重复写主存;
- 不合适的替换算法可能让热点块频繁被赶走;
- 写策略和映射方式组合不同,整体表现也会不同。
所以这部分不是“记几个术语”,而是在理解缓存系统如何真正做工程权衡。
十二、从工程角度看,这部分为什么重要
理解写策略和替换算法,会帮助你更自然地理解很多真实问题:
- 为什么写密集型程序的缓存行为和读密集型不同;
- 为什么某些访问模式会制造大量不必要的缓存替换;
- 为什么看起来一样的 Cache 容量,在不同策略下表现不同;
- 为什么多级缓存和一致性问题会逐渐复杂起来。
十三、学习这一部分最容易踩的坑
1. 把写直达和写回只理解成“一个快一个慢”
它们更准确地说是不同负载场景下的不同工程权衡。
2. 忽略写不命中的装载策略
写分配和非写分配同样会显著影响行为。
3. 以为替换算法只是附属规则
实际上它会直接改变 Cache 的真实命中率表现。
总结
Cache 写策略和替换算法,决定了缓存系统在修改数据和空间不足时如何运转。真正值得先抓住的,是这些认识:
- 写策略要解决的是 Cache 与下层存储的数据同步问题;
- 写直达强调立即同步,写回强调延后回写;
- 写分配与非写分配决定写 miss 时要不要把块装进 Cache;
- 替换算法决定空间不足时谁被淘汰;
- 这些策略共同影响 Cache 的性能、一致性维护和工程复杂度。
把这一篇理解透之后,下一步再看虚拟存储时,就会更容易感受到:现代计算机存储系统的复杂性,绝不只是“快慢分层”,而是在不断处理映射、同步、替换和抽象之间的平衡。
参考资源:
- 《深入理解计算机系统》
- 《计算机组成与设计:硬件/软件接口》
- Wikipedia - Cache replacement policies
- Computer Systems: A Programmer’s Perspective