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 的性能、一致性维护和工程复杂度。

把这一篇理解透之后,下一步再看虚拟存储时,就会更容易感受到:现代计算机存储系统的复杂性,绝不只是“快慢分层”,而是在不断处理映射、同步、替换和抽象之间的平衡。

参考资源