per-node objcg
memcg 对象计费
obj_cgroup
原本一个 memcg 一个
拆分
01b9da291c49
per-NUMA-node
一个 memcg 多个
reparent 取 node 锁
目的
降低锁竞争
每个 node 一个 objcg
对象归属更细
stock 要跟着变
背景
一个 memcg
变成多个 objcg
旧 stock · 单槽
obj_stock_pcp
per-CPU 缓存
只存一个 objcg
字段
cached_objcg
nr_bytes
cached_pgdat
slab stats
命中
同 objcg
直接加减
不碰全局计数
换 objcg 就 drain
单槽
一个 CPU 只记一个
换目标就清空
回归 · 跨节点颠簸
同一 memcg 线程
跑在不同 NUMA node
同一个 CPU 计费
切换
node A objcg
node B objcg
stock 只有一个槽
每次换都 drain
结果
drain 旧
refill 新
反复 thrash
ops_per_sec -67.7%
实测
stress-ng.switch
吞吐回归 67.7%
方案 · 缩字段
先不扩 stock
把单槽字段压小
给 5 slot 腾空间
指针 → id
cached_pgdat
8 字节指针
int16_t node_id
NUMA_NO_NODE
计数 16 位
nr_bytes
uint16_t
slab stats
int16_t
三连缩
node_id / nr_bytes / stats
先省出空间
布局 · 64 字节
pahole 验证
非 debug 64-bit
热路径一行
第一行
lock + index
node_id + stats
nr_bytes[5]
cached[5]
第二行
work_struct
flags
drain_idx
冷字段
目标
consume / refill / account
各触一行
多槽 · 5 slot
NR_OBJ_STOCK = 5
cached[5]
nr_bytes[5]
同时驻留
同 memcg 各 node 变体
加几个 sibling
不再强制 drain
一颗 CPU 握住
插入策略
先找同 objcg
再用空槽
满了轮转驱逐
drain_idx
关键
多槽共存
消除换槽抖动
热路径 · 扫描/驱逐
consume_obj_stock
refill_obj_stock
__account_obj_stock
consume
扫 cached[5]
命中槽
nr_bytes[i] -=
不够就慢路径
refill
扫同 objcg
空槽插入
满槽 drain 一个
再 refill
细节
stats 绑定 index
换槽先 flush
收益与边界
stress-ng.switch
同 memcg 跨 NUMA
ops_per_sec
回归
-67.7%
单槽换 objcg
drain/refill thrash
多槽后消除
边界
256KiB 页例外
nr_bytes 仍 uint
MAX_NUMNODES < S16_MAX
非 debug 64-bit
结论
只报吞吐回归
不外推
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x