sheaf:per-CPU 的对象批量缓存
容量固定:装对象指针,用满即止
prefill API 一次预取一批,用完批量归还
调用方(如 maple_node)
经 prefill API 批量取用、批量归还
绕开逐对象的慢路径
分配与释放都走批量
barn:每 node 的 sheaf 仓库
full 与 empty 两个列表
供 CPU 之间互通有无
full
empty
补充只从 partial slab 取
从不消费 barn 的 full 列表
偏向就在这里
sheaf 未满时
只从 partial slab 补对象
补充要走 list_lock
拿对象本身就不便宜
full 列表:只进
kfree_rcu 的 RCU sheaf 往里放
却没有出口
一旦饱和就一直饱和
RCU sheaf
kfree_rcu 聚起来的对象
塞不进 barn
full 列表已经没有位置
逐个 flush 回 slab
对象回流走慢路径
快路径的成果,最后又还给了慢路径
refill_sheaf_from_barn()
在 barn 锁内循环拷贝
直到 sheaf 填满,或 barn 被掏空
原地填充
调用方手里的 sheaf 始终不换出
每一轮的拷贝源
先取 barn 的 partial sheaf
没有,就从 full 列表取一个
full 列表上的 sheaf 未必是满的
每轮按「缺口」与「源存量」的较小值拷贝
源拷贝后还有剩
就留作 partial sheaf
源被掏空
就放回 empty 列表
barn 掏不出对象时返回 false,回退原有的 partial slab 路径
把未满的 sheaf 直接挂上 full 列表行不通
它占着位置,full 列表依旧饱和
RCU sheaf 照样无处安放
partial sheaf 收留剩余对象
既不丢回 slab
也不占 full 列表名额
位置照样腾得出来
__pcs_replace_empty_main()
拿空 sheaf 换 barn 的 full sheaf
不会有剩余
refill_sheaf()
只从 partial slab 取
不碰 partial sheaf
只有 prefill 路径需要这个 partial sheaf
方案 A:直接换 sheaf
调用方的 sheaf
barn 的 full sheaf
代价:丢失 cache 亲和性
收益只是省一次 memcpy
方案 B:原地填充
调用方的 sheaf
barn 里的对象
实测两者没有可测差异
于是采用原地填充,更直白
换 sheaf 原本还要附加两件事
pfmemalloc 约束、容量修正 —— 现在都不需要了
每取走一个 full sheaf,就给未来的 RCU sheaf 腾出一个位置
barn 从只进不出,变成有进有出
refill 取走 full sheaf
少一个占位
full 列表腾出位置
有了空位
RCU sheaf 进 barn
免于 flush 回 slab
从 barn 拿对象更便宜
不必在 list_lock 下走 partial slab
两侧都受益
腾位置 + 拿得快
barn 的循环被打通了
will-it-scale mmap1
192 个进程,每次一分钟
maple_node cache
baseline 与 patched 对比
吞吐
27778727 → 34500556
+24.2%
alloc_slab / free_slab
7100651 → 159739(-97.75%)
7099788 → 158297(-97.77%)
page allocator churn
14200439 → 318036
-97.76%
free_slowpath
10969156 → 782851
-92.86%
refill / flush / barn_put_fail
-44.18% / -44.12% / -44.12%
代价:slab 1604 → 2336
shrink 后 485 vs 563
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x