每个 memcg × 每个 shrinker,记两笔账
bitmap:这个 memcg 是否持有该 shrinker 的对象
nr_deferred:欠这个 memcg 多少可回收对象
1 个 shrinker_info_unit
64 个 shrinker id 的账本打包在一起
unit 内部:64 个计数器 + 1 个 bitmap 字
bitmap 字 = 8 B
64 × atomic_long_t = 512 B
520 B
kmalloc 按 2 的幂分槽
32
64
128
256
512
1024
2048
1 KB 槽
520 B
520 刚过 512 的槽位上限,只能落进下一档
一个 unit 占满 1 KB 槽,近一半是 padding
520 B
近一半 padding
1 KB 槽
槽位按 1 KB 记账,空出来的部分不装任何东西,也不可回收
unit 数 = cgroup 数 × node 数 × id 段数
一个方块 = 一个 cgroup 在一个 node 上
方块里的小格 = 每条 id 段上的 unit
三者相乘就是预分配的 unit 总量
node 数
cgroup 数
memcg 一上线,就把覆盖到的 unit 全部预分配
cgroup 上线
每个 NUMA node
按 shrinker_nr_max 覆盖到的 unit 一次全分配
空 cgroup 也一样付费,这笔内存不可回收
给 unit 建一个专用 kmem_cache
kzalloc_node(size, gfp_mask, nid)
按 2 的幂分槽:520 → 1 KB
换成
kmem_cache_alloc_node(shrinker_unit_cachep, ...)
按 520 字节实际大小分槽
cache 在 memcg 子系统初始化时创建,释放走 kmem_cache_free()
同一个 order-2 的 16 KB slab:16 个 vs 31 个 unit
kmalloc-1k:16 个 unit
专用 cache:31 个 unit
同样 16 KB,密度接近翻倍
unit 内部本来就在共享 cacheline
一个 cacheline 里挤着多个 shrinker 的计数
对齐 unit 边界只能让相邻 unit 不跨线,收益有限
取舍:不做 cacheline 对齐
不对齐
520 B,装得下 31 个
对齐
520 被重新撑大
对齐收益有限,撑大却直接吞掉一部分节省
实测:4-CPU 单 node 虚拟机
8000 个 tmpfs 挂载
挂载越多,id 段越多
2000 个空 memcg
空 cgroup 也得照付
统计口径:每个 cgroup 的 unreclaimable slab
每个空 cgroup 的 unreclaimable slab
Before
144 KB
After
87 KB
−39.6%
功能验证
memory.reclaim
140 个 ext4 superblock 上的对象照样回收
drop caches
删 cgroup 后新 cache 回到原大小
slub_debug=FZPU
抬高 shrinker_nr_max 时首次分配无异常
test_memcontrol
selftests 通过
按实际大小分槽
520 B 不再进 1 KB 槽
16 → 31 个
同一个 16 KB slab
144 → 87 KB
每个空 cgroup 的 slab
省下的是分槽的 padding,unit 一个字节都没动
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x