SLUB percpu sheaves
每 CPU 一个对象缓存
分配先看 sheaf
空或满再找节点仓
barn 节点仓
缓存 full / empty sheaf
由 barn 锁保护
服务节点本地对象
sheaf 在 CPU 与 barn 之间流动
快路径
sheaf 命中直接返回
不碰 slab 慢路径
分配与释放都受益
热点路径的关键
前提:CPU 所在节点要有 barn
没有 barn,sheaf 无法回填或排空
快路径就断了
旧结构
struct kmem_cache_node
barn 指针挂在里面
只有有内存节点才实例化
建仓路径
init_kmem_cache_nodes()
遍历有内存节点
分配 kmem_cache_node + barn
无内存节点不建
设计目标
只缓存节点本地对象
无内存节点没有本地对象
于是不建仓
在无内存节点上说不通
结果
无内存节点的 CPU 拿不到 barn
sheaves 失效,分配退回慢路径
Ming Lei 报告的回归
8 个 online NUMA 节点
只有 2 个有内存
slub 统计大量慢路径
原因
无内存节点没有 barn
sheaves 用不了
每次分配走慢路径
性能被拉低
节点越多越明显
更多 CPU 落在无内存节点
失效范围更大
8 节点 2 内存只是示例
多节点无内存会放大
目标
让无内存节点也有仓
分配与释放都回到 sheaf 快路径
解耦仓指针
struct kmem_cache_per_node_ptrs
barn 与 kmem_cache_node 分开
每个在线节点可独立持有
per_node 数组
原来 node[] 按 nr_node_ids 分配
不能简单再加一个数组
合并成 per-node 指针结构
barn 与 node 各一个指针
访问器
get_barn_node(s, node)
按节点取 barn
不再依赖 kmem_cache_node
指针路径先拆开
第一步无功能变化
仓仍 1:1 跟随节点创建
为下一步所有在线节点建仓铺路
新节点位图
slab_barn_nodes
记录哪些节点有仓
对应 N_ONLINE
初始化
for_each_online_node()
所有在线节点入位图
init_kmem_cache_nodes() 建仓
bootstrap 也覆盖
CPU 热插拔
slub_cpu_setup()
新 CPU 上线时补建
无内存节点随 CPU 上线
不遗漏在线节点
所有在线节点都有 barn
不再只限 N_MEMORY
代价是 barn 内存略增
旧归还落点
numa_mem_id()
最近的有内存节点
无内存节点的 sheaf 收不到对象
改为本 CPU 节点
get_barn() → numa_node_id()
rcu_sheaf->node 也改
对象进入本节点仓
可能正是无内存节点
free_to_pcs
对象进入本 CPU sheaf
换 main 时用本地 barn
不再借最近有内存节点
仓不再是空壳
无内存节点也收对象
分配与归还落在同一节点仓
sheaves 在本地 CPU 上转起来
释放侧局部性
can_free_to_pcs()
判断对象所在 slab 节点
决定能否进 per-CPU sheaves
HAVE_MEMORYLESS_NODES=y
仍用 numa_mem_id()
只收最近内存节点对象
局部性判定不变
这类系统不是主要目标
HAVE_MEMORYLESS_NODES=n
本 CPU 节点无内存
远端对象也允许进 sheaf
避免全部走慢路径
条件:本节点无 N_MEMORY
统一成一条规则
各释放路径共用 can_free_to_pcs()
代价:对象平均距离变大
预期收益
无内存节点分配走 sheaf
释放也走快路径
慢路径统计应下降
HAVE=y 的额外收益
每个节点独立 barn
不再挤最近内存节点的锁
barn 锁竞争下降
代价是 barn 内存略增
代价
远端对象进 sheaves
分配到的对象平均距离变大
原先靠 zonelist 回填更近
有意取舍,不是缺陷
一句话
所有在线节点建仓,按本 CPU 定位仓
无内存节点释放放宽局部性;文章无量化 benchmark
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x