背景
内存分配剖析(MAP)的数据经 /proc/allocinfo 暴露给用户态。这个文本接口手工查看够用,放到生产监控与大规模分析里就吃力了。
文本接口的全量开销
┌────────────────────────────────────┐
│ read() 对上千个 tag 逐个聚合全 CPU │
│ 计数器 │
└─────────────────┬──────────────────┘
▼
┌────────────────────────────────────┐
│ 全量文本拷贝到用户态 │
└─────────────────┬──────────────────┘
▼
┌────────────────────────────────────┐
│ 用户态再解析过滤,多数数据白跑一趟 │
└────────────────────────────────────┘
问题
- 用户态要解析大量文本才能取出要的字段
- 找特定 tag 得全量读取,上下文切换与数据拷贝多
- 内核为每个 code tag 聚合全 CPU 的 per-CPU 计数器,哪怕用户马上把它过滤掉
方案
给 /proc/allocinfo 加 IOCTL 二进制接口,把过滤条件下推到聚合之前:内核只对命中条件的少数 tag 做 per-CPU 计数器聚合,不命中的完全不碰。
过滤条件下推到聚合之前
┌──────────────────────────────────┐
│ ioctl 先下推过滤条件 │
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 只对命中 tag 聚合 per-CPU 计数器 │
└────────────────┬─────────────────┘
▼
┌──────────────────────────────────┐
│ 二进制记录按需取,文本解析全省 │
└──────────────────────────────────┘
接口提供 3 个命令:ALLOCINFO_IOC_CONTENT_ID 取内容标识,模块加载卸载会让它变化,用户在读取前后各取一次即可校验数据一致性;ALLOCINFO_IOC_GET_AT 按位置取记录;ALLOCINFO_IOC_GET_NEXT 取下一条记录。
过滤维度逐步铺开:按 tag 的模块名、函数名、文件名、行号过滤(这类名字常有公共前缀,比较时取末 64 个字符降低碰撞);按 [min_size, max_size] 尺寸区间过滤,这个区间含端点,且因为要真的取出计数器,比其他过滤慢;按 accuracy 过滤。
收益
作者在 Intel Xeon Platinum 8481C(224 CPU)上测量,每次运行前 drop caches,sys 时间:
| 场景 | 传统 read() | IOCTL | 改善 |
|---|---|---|---|
| 按文件名过滤 | 22ms | 1ms | 约 22 倍 |
| 文件名加尺寸复合过滤 | 21ms | 1ms | 约 21 倍 |
| 按尺寸过滤 | 21ms | 14ms | 1.5 倍 |