新对象先试用。没有很快被复用,就尽快淘汰,避免污染主缓存。
1. LRU 像一家按“最近来过”排队的会员店
假设店里只能留 100 位顾客。LRU 认为:刚来过的人更可能再来,所以每次顾客出现,都把他移到队伍最前面。
问题是,一场旅游团突然涌入:每个人只来一次,却会把老顾客不断挤出去。论文把这类只出现一次、之后不再复用的对象叫作 one-hit wonder。
作者分析的 6594 条生产 trace 中,全 trace 的 one-hit wonder 比例中位数是 26%;当窗口缩短到每条 trace 唯一对象数的 10% 时,中位数升到 72%。这解释了为什么“尽早清掉只访问一次的对象”特别重要。
2. 两个动作,比复杂预测更重要
命中时不急着移动对象,只记一个很小的热度计数;等到淘汰时再处理。
队列大小固定,减少自适应参数和过度反应,行为更容易预测。
这三点合起来,就是名字里的三个 S:Simple、Scalable、Static。算法只用 FIFO 队列,但“何时快速放弃、何时有限续命”并不等同于普通 FIFO。
3. 三个队列,是三道不同的门
约占数据缓存 10%。新对象先来这里;很快被重复访问才有资格晋升。
约占数据缓存 90%。保存已经证明有复用价值的对象。
只保存被 S 淘汰对象的名字,不保存数据;容量与 M 的对象数相当。
G 很容易误解。它不是第三级数据缓存,而是一张便宜的“后悔记录”:某对象刚被淘汰又回来,说明 S 误判了它,下一次直接送入 M。
4. 把算法压缩成四条规则
- 命中 S 或 M:热度计数加 1,最高为 3;对象不挪位置。
- 未命中且 G 不认识:当作新人,放到 S 的队头。
- 未命中但 G 认识:说明刚才淘汰得太早,直接放到 M。
- 需要腾空间:S 中复用足够的对象进 M,其余只留名字到 G;M 中热度大于 0 的对象减 1 后回队头,热度为 0 才真正淘汰。
注意一个细节:S 中对象要在淘汰检查前被再次命中两次,即计数大于 1,才直接晋升 M。M 则像“有几张续命券”:每次轮到淘汰时消耗一张,而不是每次命中都改队列。
5. 亲手推演:队列何时发生变化?
下面把缓存缩成 4 个对象、S 阈值缩成 1,方便观察。真实论文默认 S:M 约为 1:9;演示比例不影响规则。
f 是封顶为 3 的访问计数;G 中只有 key,没有对象数据。
6. 为什么它又快又准?
更准,不是因为 FIFO 会预测未来
S 把大量只来一次的对象挡在主缓存外;G 又为“被误伤但很快回来”的对象提供纠错。M 只需要在淘汰时依据两位计数决定是否续命。
更快,是因为命中路径很安静
LRU 每次命中都要把对象移到队头,多线程下会争用同一份队列元数据。S3-FIFO 命中时通常只做一个封顶计数的原子更新;热门对象计数到 3 后,连计数也无需继续变化。论文的 Cachelib 原型在 16 线程上达到优化 LRU 的 6 倍以上吞吐量。
7. 别把论文结论读成万能定律
此外,论文的“6 倍吞吐”来自特定 Cachelib 原型、合成 Zipf trace 和 16 线程环境;它证明了设计的扩展性潜力,不等于任何系统换上 S3-FIFO 都自动快 6 倍。对象大小、显式删除、TTL、预取和缓存层级都会改变结果。
所以正确的论文结论是:在大量真实 trace 上,quick demotion 是一个强而稳健的信号,三个静态 FIFO 队列能以很低的命中路径成本利用它。
1 分钟检验
问题:S3-FIFO 最重要的第一性洞察是什么?
接下来可打开 S3-FIFO 一页速查表,然后不看答案,试着给我解释 S、M、G 各自解决什么问题。我会根据你的解释纠正遗漏。
首选原始资料
推荐先读作者论文 《FIFO Queues Are All You Need for Cache Eviction》 的摘要、第 3 节和第 4.1 节。课程中的比例、伪代码与实验数字均据此核对;官网的 作者长文 更适合第一次通读。