zswap shrinker
池子要腾空间,触发 writeback
分配 swap cache folio
解压 cold entry
swap cache folio
从构造上就是冷的
只为承载写回而生
LRU 链表
cold folio 留在链上
等 page reclaim 扫到
写回完成即冷,却无人释放
同一个 cold folio 的两条去向
现在
writeback
留在 LRU
page reclaim 扫描
本应
writeback
dropbehind 立即释放
不再占 LRU / 不再被扫描
每写回一个 cold folio
kernel build · pgrotated
每次 writeback 多 1 次 page reclaim 扫描
441028
并推迟真实热数据的回收
cold folio 被反复扫描旋转
越晚释放,扫描旋转越久
既有 dropbehind 机制
PG_dropbehind
writeback 完成即 drop
file page
既有用户
扩展
swap cache folio
写回完成从 cache 移除
core-mm 通用机制,由 zswap 选用
swap cache 分配器
folio_add_lru()
per-CPU 批次留下引用
移到调用方
写回即 drop 的 folio
可以「离开 LRU」分配
分配函数改名
__swap_cache_alloc_folio()
drop 要拿两把锁
swap cluster 锁
关中断自旋锁,不睡眠
folio 锁
可能睡眠等待
writeback 完成
可能在中断上下文
那里不能睡眠
BIO_COMPLETE_IN_TASK
完成回调改在进程上下文
folio_end_writeback() 就地 drop
remove_mapping_set_shadow()
从 swap cache 摘下
存下 workingset shadow
swap-in refault
按 refault 检测
重新激活
被 swap-in 抢走
擦掉标记
放回 LRU
zswap 的 writeback folio
离开 LRU 分配
__swap_cache_alloc_folio()
写回即 drop 的 folio
打上 PG_dropbehind
选用通用机制
swap cache 持有唯一引用
drop 时引用计数对得上
放掉 zswap 引用
zswap 侧引用已清零
发起 writeback
同步 IO 立即完成
完成回调 drop
remove_mapping_set_shadow() 成功
晚一步放引用,drop 就会失败
kernel build · defconfig · make -j4 · memory.max=600M
Before
After
改善
pgrotated
441028
→
2521
-99.4%
pgsteal_direct
3524343
→
2869004
-18.6%
pgscan_direct
8393791
→
7765008
-7.5%
zswpwb
705129
→
699019
-0.9%
构建耗时
1155s
→
1114s
-3.6%
writeback folio 及时释放
698990 / 699019
99.996%
仅 28 个回退
MySQL/OLTP · sysbench · 10 表 × 1M 行 · 512M buffer pool · 8 线程 · memory.max=256M
Before
After
改善
transactions/s
153.87
→
163.30
+6.1%
p95 延迟
157.42ms
→
145.82ms
-7.4%
平均延迟
52.09ms
→
49.05ms
-5.9%
同一轮测试 · 扫描计数
pgrotated
743738
→
22460
-97.0%
pgsteal_direct
6886490
→
5445278
-20.9%
pgscan_direct
13462510
→
10820730
-19.6%
同步 IO zram 的及时释放比例
zram 实际
99.999%
对照 · 放上 LRU
2.7%
引用计数回退仅触发 37~50 次
推广到全部回收 swap folio
sysbench OLTP · NVMe swap
吞吐回退约
-15%
一般回收要靠 swap cache 捕获 refault
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x