struct mem_cgroup
每个 cgroup 一个
charge / 回收 / socket 计费 / 统计读取
struct mem_cgroup_per_node
每个 cgroup 每个 node 一个
同样高频
各路径访问模式差别很大
有的字段每次回收迭代都写
有的创建后只读
有的只有 cgroup 控制路径才碰
CPU 0 写字段 A
一条 cache line 上
同线字段 B 的副本失效
在其余 CPU 的缓存里
字段按声明顺序排布
访问模式不同的字段很容易落进同一条 cache line
false sharing
一侧高频写,另一侧的读路径被反复拖慢
新增、删除字段或 config 变化
结构体布局整体挪动
访问模式不同的字段落进同一条 cache line
一侧频繁写
其余 CPU 的读路径缓存反复失效
手工补 padding 修复
下一次挪动又可能复发
98c9daf5ae6b
把 lruvec 挪到 lru_zone_size[] 旁边,引发回归
f59adcf59332
补 padding 修复
c1afbd5de131
在 vmstats 与 events 之间加 ____cacheline_aligned_in_smp
每个修复都正确
但没有机制阻止下一个字段再破坏布局
从隐式约定到编译期契约
三组宏搭成契约,做法与 struct net_device 的 cacheline group 相同
__cacheline_group_begin_aligned
零长标记
+ SMP_CACHE_BYTES 对齐
组内字段从新线开始
__cacheline_group_end_aligned
组尾补 padding
后续字段掉不进
组的最后一条线
CACHELINE_ASSERT_GROUP_MEMBER
展开为 BUILD_BUG_ON
校验字段偏移
落在组标记区间内
memcg_struct_check()
集中列出全部已分组字段,由 mem_cgroup_init() 调用
放错位置直接编译失败,不再静默流失性能
current_obj_cgroup() 每次计费都读 objcg
原在 per_node 尾部,与 lru_zone_size[]、iter 同线
先搬到头部只读指针旁
struct mem_cgroup_private_id 拆开
id:创建与销毁时写,驱逐与 refault 都读
ref:每次 swap charge 与 uncharge 都写
一个成员无法跨两个组 → private_id / private_id_ref
两处先行调整:先让每个字段只属于一种访问模式
css 与各 page_counter
保持原位
write_hot
socket_pressure、memory_events
vmpressure、private_id_ref
charge、回收、socket 频繁写
cold
events_file 等三个 cgroup_file
cgwb 系列、mm_list
写不再波及快路径
read_mostly
vmstats_percpu、private_id
kmemcg_id 等
创建后只读
nodeinfo 柔性数组
同为只读,组尾不补 padding,与 read_mostly 组共享一条线
socket_pressure 与 memory_events 同线:一读一累加,省一次取线
vmstats 旧对齐只管开头,cgwb_domain 仍同线;组边界两头都管
read_mostly
反向指针与统计指针
创建后只读
lruvec 独占 cache line
lru_zone_size[] 在 lru_lock 下写
lruvec_lru_size() 无锁读,同线曾致回归
write_hot
LRU 更新与回收迭代都写
lru_zone_size、iter、NMI slab 统计
cold
reparent 与释放时才触碰
orig_objcg、objcg_list
cold 组独立出来的代价:每 node 每 memcg 多占一条 cache line
两台同配置 30 核 Xeon
新旧内核互换启动顺序,机器与顺序影响相互抵消
一个 cgroup 内并发两个负载:false sharing 需一读一写并存
负载组合
提升
slab 分配侧,与 page fault 并发
+1.2%
page fault 侧,与 memory.stat 读取并发
+1.3%
memory.stat 读取侧,与 page fault 并发
+0.8%
其余组合:无变化
cgroup selftests 新旧内核结果一致;绝对提升不大,重心是编译期契约
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x