memcg 逐页计费太贵 → 按批预收
try_charge_memcg()
一次收 MEMCG_CHARGE_BATCH
也就是 64 页
page_counter
memcg 的用量计数
先预收一整批
refill_stock()
用不完的余额存进 per-CPU stock
每个 CPU 一份 memcg_stock_pcp
consume_stock()
后续 charge 先吃余额
吃中就不必碰全局计数器
核心:把逐页计费压成一次批处理
省掉的是对全局计数器的频繁触碰
每个 CPU 的 stock 有 7 个 slot
NR_MEMCG_STOCK = 7,为的是挤进一条 cacheline
slot
slot
slot
slot
slot
slot
slot
指针数组与余额数组同住一条 cacheline
一个 slot 记两样
cached[]
→ memcg 指针
nr_pages[]
→ 余额页数
两样一起决定 slot 的状态
slot 的占用与释放
都伴着一次 css 引用操作
引用是 slot 的伴生账
refill_stock() 占 slot
占之前先 css_get()
同 memcg:直接累加余额
否则找空 slot;找不到
就按 drain_idx 轮转 drain 一个
drain_stock() 清 slot
余额退回 memcg 的 page_counter
css_put() 放引用
cached[] 置 NULL
slot 空出来
memcg offline:drain_all_stock() 扫过所有 CPU
把有余额的 slot 全部清掉
引用放完,memcg 才真正释放
nr_pages[] 非零?
是:drain_stock()
退余额、放引用
否:跳过
不在清理名单
offline 时 drain_all_stock() 也按这条判据扫
空壳没有余额,offline 不会清它
判据本身没错:有余额才需要退
可余额归零的 slot,也还占着 cached[] 和引用
空壳 slot
nr_pages[] = 0
cached[] → memcg A
css 引用没有放
consume_stock() 减到 0 就停手
drain 的判据
只认 nr_pages[] 非零
空壳永远走「跳过」
引用只剩两条偶然释放路径
① 被无关 memcg 的 charge 轮转挤出
② CPU 下线
memcg 只能滞留在 dying
等偶然事件才被释放
空壳 = 余额为零,引用还在
容器频繁创建销毁
空壳在每个 CPU 上累积
dying
dying
dying
dying
dying
dying
dying
每 CPU 最多滞留 7 个 dying memcg
空壳不能被 refill_stock() 复用
新 charge 想找空 slot,找不到
只能按 drain_idx 驱逐一个有余额的 slot
引用滞留 + slot 浪费,两头都亏
留着空壳只省下同 memcg 下次的一对 css_get()/css_put()
归零时顺手把引用放掉
if (!stock_pages) {
css_put(&memcg->css);
WRITE_ONCE(stock->cached[i], NULL);
}
slot 清空
css_put(&memcg->css) 放引用
cached[] = NULL
空壳不复存在
stock_pages 是扣减后的余额
slot 只剩两种形态
有主且有余额
正常记账
完全空闲
可被直接复用
refill_stock() 直接复用空 slot
drain 路径保持原样,判据仍是「有余额」
有余额的 slot 不再被无关 charge 轮转误逐
代价
每清空一个 slot 多一对引用计数操作
最多每 MEMCG_CHARGE_BATCH 页一次
归零即放之后
引用随清空即放
offline 后不再滞留 dying
该释放时就释放
空 slot 直接复用
有余额的 slot 不再被误逐
新 charge 不必驱逐好 slot
drain 判据不变
仍只看「有余额」一条
不用识别空壳
作者未提供性能数据
以上收益由代码逻辑推断,不是实测数字
空壳不该存在
余额归零 → css_put() → cached[] = NULL
slot 要么有主有余额,要么完全空闲
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x