memcg 的统计有两种口径
层级值包含整棵子树;非层级值只算 memcg 自己
memcg_vmstats
两份数组,口径不同
层级值
state / events
包含整棵子树;v2 memory.stat 读取
非层级值
state_local / events_local
只算 memcg 自己;v1 接口与 offline reparenting 读取
层级值给 v2;非层级值的读者是 v1 接口与 offline reparenting。
一次 flush,同一份差值写两套数组
rstat flush 聚合 per-cpu 差值时,层级与非层级各更新一次
mem_cgroup_stat_aggregate()
遍历全部 77 个 memcg 范围统计项:57 state + 20 events
层级数组
state / events
累加 per-cpu 差值;v2 memory.stat 读取
非层级数组
state_local / events_local
同一份差值再做一次读改写
非层级数组没有读者时,这次读改写纯属浪费。
v1 有两种不可用的形态
无论哪种,非层级数组都没有读者
CONFIG_MEMCG_V1=n
读取函数 memcg_page_state_local() 等直接编译消失
数组照样被写
cgroup_no_v1={memory,all}
v1 挂载被拒绝
legacy 接口根本不会出现
数组同样只写不读
两种形态下非层级数组都没有读者,flush 却照样逐次写入。
编译期:让两个 local 字段直接不存在
CONFIG_MEMCG_V1=n 时,用 #ifdef 从 memcg_vmstats 中移除
memcg_vmstats
v1 编译启用
state
events
state_local
events_local
CONFIG_MEMCG_V1=n
local 字段不编译
state
events
不占内存,也无从写入
#ifdef
字段在编译期消失,连写入目标都不存在。
运行期:v1 编译启用,但 cgroup_no_v1= 封锁 memory
非层级数组指针改由 memcg_state_local_array() 提供
static long *memcg_state_local_array(struct mem_cgroup *memcg)
{
#ifdef CONFIG_MEMCG_V1
if (!cgroup1_ssid_disabled(memory_cgrp_id))
return memcg->vmstats->state_local;
#endif
return NULL;
}
cgroup_no_v1= 封锁 memory
cgroup1_ssid_disabled() 为真
→ 返回 NULL
聚合循环见到 NULL
跳过 local[i] 的读改写
层级数组照常聚合
空指针是跳过信号,不是错误。
flush_nmi_stats() 的写入也先判空;events 一侧由 memcg_events_local_array() 对称处理。
收益:80 核上的单次 flush 耗时
500 个 cgroup 持续更新计数;TSC 计时,70 个样本取中位数
指标
Before
After
改善
memcg 范围聚合(TSC tick)
1231
1180
4.1%
flush 函数整体(TSC tick)
2452
2397
2.2%
基准循环读 memory.stat,维持高频 flush。
每次 flush 中位数只有 1.5 个统计项有非零差值,相当于少拉入一至两条 cacheline。
若只由 2 秒周期刷新触发,差值积累更多,单次收益更大。
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x