per-cpu charge stock:批量摊销
charge 1 页先批量拿 64 页,把全局计数开销摊薄
charge 1 页
memcg 按页 charge
批量 charge 64 页
MEMCG_CHARGE_BATCH,63 页缓存进 stock
后续 charge / uncharge
在 stock 内就地增减
不足或溢出才碰全局 page_counter
批量摊销把 64 页的全局计数开销摊到每次 charge。
obj_stock 缓存 obj_cgroup 指针,是另一套设施,与 charge stock 无关。
一个常数,两个角色
MEMCG_CHARGE_BATCH 既是高水位,又是清空目标
高水位
MEMCG_CHARGE_BATCH = 64
计数到这里就该处理
清空目标
MEMCG_CHARGE_BATCH = 64
处理方式:整体清空
越界时
计数 > 64 → 整个 stock 清空
下次 charge 重新未命中
高水位与清空目标同值,越界没有中间态。
write/read 交错,正好踩中越界
request/response 型负载:每轮 write 1 页、read 2 页
write:charge 1 页
批量拿 64 页
stock 缓存 63 页
read:uncharge 2 页
stock 63 → 65
越过 64 上限
下一轮 charge
stock 已整体清空
再次未命中,重新批量拿 64 页
stock 持续清空、重建,批量摊销完全失效。
拆成两档:HIGH 与 LOW
借用页分配器 per-cpu cache 的水位分离
两档水位
MEMCG_STOCK_HIGH = 64
MEMCG_STOCK_LOW = 32
超过 HIGH 才触发修剪
修剪动作
只把超出 LOW 的部分回充 page_counter
stock 始终留存 32 页余量
每批 charge 仍是 64 页
上限仍是 64:峰值缓存页数不增,批量摊销语义不变。
越界只修剪到 LOW
同样的 write/read 交错,stock 不再清空
uncharge 2 页:计数 65
越过 HIGH=64
只修剪到 LOW=32
超出 LOW 的部分回充 page_counter
每轮 write/read 净攒 1 页
stock 保持命中,不再清空重建
再攒 32 轮才第二次修剪
两次修剪之间,stock 全程保持命中
越界只修剪,不清空;32 页缓冲垫住交错流量。
收益:问题只出现在特定消息尺寸
作者给出的修复前观测;request/response 型负载
指标
问题消息尺寸区间
区间外
memcg charge/uncharge 路径 CPU 占比
44.6%-57.0%
0.27%-0.71%
从 root memcg 运行(跳过 socket 记账)性能恢复,问题由此定位到 charge/uncharge 路径。
作者未提供修复后的实测数据。
按代码逻辑,stock 持续命中后,charge 路径开销回到区间外水平。
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x