背景
内核把刚分配或刚解除映射的页先攒在 per-CPU 的 lru_add 批量里,等攒够或需要时一次性排空(drain)进 LRU。这个批量里有时混着"死 folio":它的 refcount 只剩下批量自己持有的那一份引用,换言之它已经没人用、迟早要被释放。
┌──────────────────────────────────┐
│ 排空时把批量里的 folio 加入 LRU │
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 其中有死 folio,只剩批量引用计数 │ 加入只为随即被移除,白拿两次锁
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 占去 lruvec 锁竞争约四分之一 │
└──────────────────────────────────┘
按旧逻辑,死 folio 照样被排进 LRU,紧接着 folios_put_refs 发现它已是死页,又把它从 LRU 摘下释放。一来一回,一个本就不该进 LRU 的页白白占用了两次 lruvec 锁。Meta 在生产环境观测到,约 24% 的 lruvec 锁竞争就出在这种"加入只为移除"的死 folio 身上。
问题
- lru_add 批量里混着只剩批量引用的死 folio
- 死 folio 照样被排进 LRU
- 紧接着又被摘下释放,白拿两次锁
- 占 lruvec 锁竞争约 24%
方案
核心是在 drain 之前就把死 folio 拣出来,让它根本不进 LRU。
旧:死 folio 进 LRU 再移除
┌─────────────────────────────────┐
│ 批量里混着死 folio │
└────────────────┬────────────────┘
▼
┌─────────────────────────────────┐
│ 加入 LRU 又立即移除,白拿两次锁 │
└─────────────────────────────────┘
新:排空前先过滤死 folio
┌──────────────────────────────┐
│ 冻结判定只剩批量引用的 folio │
└──────────────┬───────────────┘
▼
┌──────────────────────────────┐ 过滤的 folio 绕过常规清理,须先清活跃与不可回
│ 移出批量直接释放,不进 LRU │ 收标志
└──────────────────────────────┘
用 folio_ref_freeze 判定一个 folio 是否"只剩批量引用",是的话就判定它已死。把这些死 folio 移出 add 批量、放到一个临时批量里直接释放,省掉"加入 LRU 再摘除"的往返,也就省掉了那两次锁获取。
一处细节要小心:被过滤的死 folio 绕过了常规 lru_add 的清理路径,而它们身上可能带着 PG_active(或经 migration 路径设上的 PG_unevictable)。这些 flag 必须在释放前手动清掉,否则会带着脏标志回到页分配器。
收益
作者在 Instagram 生产 workload(高频短生命周期请求)上做 A/B(classic LRU,60 hosts each side,3 × 60s,95% CPU load),用 mm_lru_insertion tracepoint 采集 per-host LRU add:
| 指标 | unpatched | patched |
|---|---|---|
| dead folios/min | 1,297,785(6.71%) | 14(0.0001%) |
| total folios/min | 19,341,986 | 19,039,996 |
patch 截获了几乎全部死 folio,每主机每分钟省下约 2.6M 次锁获取。系统级内存指标(patched 侧,95% CPU)也连带改善:
| 指标 | 变化 |
|---|---|
| direct reclaim scanning | -7% |
| allocation stalls | -5.2% |
| compaction stalls | -12.3% |
| page frees | -4.9% |
requests served/sec 与 p99 尾延迟无回归,更高 CPU 利用率(85%→95%)下两者均呈方向性改进。
需说明:测试用的是 classic LRU(非 MGLRU),结果反映的是 classic LRU 下的 lru_add drain 路径。