struct mem_cgroup
两类访问频率都很高的成员
vmstats_percpu
指针 · 统计路径每步都加载
memory_events_local[]
计数 · MEMCG_SWAP_FAIL 写入
…其余成员…
统计路径
读/写计数前先加载该指针
swap 失败事件
直接写 MEMCG_SWAP_FAIL
一个反复读取的指针,一个随时写入的计数
两者都在 memcg 统计路径上,都是热点
cgroup v2 瘦身 · CONFIG_MEMCG_V1 关闭
struct vmpressure
112 B
瘦身之后
24 B
v1 的 eventfd 处理搬进 memcontrol-v1.c
cacheline
memory_events_local[]
MEMCG_SWAP_FAIL · 写
vmstats_percpu
统计指针 · 读
两个热成员落在同一行
成员上移是瘦身的副作用,本身不致命
致命的是两个热成员从此共享缓存行
false sharing
MEMCG_SWAP_FAIL
写 · 事件计数
vmstats_percpu
读 · 统计指针
同一条 cacheline
CPU A
swap 失败,写事件计数
这条线失效
CPU B
统计路径读该指针
miss 重载
stress-ng mremap · swap 关闭走 MADV_PAGEOUT
2000 万次
约每 60 秒的 MEMCG_SWAP_FAIL 更新
176 CPU 单 socket 测试机 · mremap 吞吐中位数(相对父提交)
−4.38%
吞吐中位数下降
写事件越频繁,读路径的 cacheline miss 越多
cgroup v2 瘦身之后
vmpressure · 24 B
cgwb_list
cgwb_list 原位置
上移
vmpressure 缩小后,原地留下对齐空隙
总大小不变
cgwb_list 移入空隙,不新增成员
测试配置下 struct mem_cgroup 大小保持不变
只把 cgwb_list 挪进空隙还不够
还差一步:让 vmstats_percpu 独占一条 cacheline
给指针加上缓存行对齐
____cacheline_aligned_in_smp
指针从此独占一条 cacheline,不再与事件计数共行
改后布局 · 两条 cacheline
事件计数器
MEMCG_SWAP_FAIL · 写
vmstats_percpu · 读
独占一行
写只让第一条线失效
读路径不再被迫重新加载指针
false sharing 消失,且没有多占一字节内存
stress-ng mremap · 176 CPU 单 socket 测试机
mremap 吞吐中位数(相对父提交)
改动前
−4.38%
改动后
0.5% 以内
boot-to-boot 波动最高 1.2%
0.5% 以内的差距落在波动范围内
补丁后的差距落在波动范围内 —— 吞吐已恢复
读热点与写热点分居两条 cacheline,零额外内存
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x