sched discussion
[PATCH RESEND v2] sched: adjust the layout of the cfs_bandwith structure to save memory
LLM 分析
Scheduler 调度器:cfs_bandwidth 结构体重新排列以节约内存
系列概况
- 标题:
[PATCH RESEND v2] sched: adjust the layout of the cfs_bandwith structure to save memory - 作者: Hongling Zeng zenghongling@kylinos.cn
- 版本: v2(RESEND)
- 规模: 单 patch,1 个文件,3 insertions(+),3 deletions(-)
- 修改文件:
kernel/sched/sched.h - 代码统计: 净变化 0 行(单纯重排),结构体大小从 240 字节降到 232 字节
- Message-ID:
20260421070717.727890-1-zenghongling@kylinos.cn - 完整性: 单封 patch 邮件,附带
Reviewed-by: Ben Segall <bsegall@google.com>;尚无 maintainer 回复或回复邮件。
补丁目的
CFS bandwidth 子系统每个 CPU 上都有一个 cfs_bandwidth 结构,记录配额、周期、节流计时器、throttled runqueue 列表等字段。
用 pahole 工具检查当前 layout 后,作者发现结构体里有三处 padding hole 累计 13 字节。
把原本靠近结构体尾部的 u8 idle / period_active / slack_started 三个标志位提前到 raw_spinlock_t lock 紧后面,能借机把 4 字节 hole 挤成 1 字节 hole,并消除原本的 5 字节 hole,从而把结构大小从 240 字节降到 232 字节,省下 8 字节。
旧流程的问题
旧布局里,紧跟 raw_spinlock_t lock (4 bytes) 之后是 ktime_t period (8 bytes),必须从 8 字节对齐边界开始,于是 lock 与 period 之间留下 4 字节 padding。
int nr_burst (4 bytes) 与下一个 u64 throttled_time (8 bytes) 之间因为 8 字节对齐要求,又是一段 4 字节 padding。
末尾 u8 idle / period_active / slack_started(合计 3 字节)之后直到 64 字节 cacheline 边界还有 5 字节 hole。
合计 hole = 13 字节,240 字节结构里只有 227 字节有效载荷,浪费超过 5%。
新流程
把 3 个 u8 字段上移到 lock 之后;它们只占 3 字节,把原本 4 字节 hole 挤成 1 字节 hole。
末尾的 nr_* int 字段和 u64 throttled_time 还在中间产生一个 4 字节 hole,但靠拢的 u8 已经多省了 8 字节。
最终 hole 合计 5 字节,结构体大小 232 字节;4 个 cacheline 数量未变,但最后一个 cacheline 由 48 字节填充缩到 40 字节填充。
关键实现
源码层面只动 6 行,diff 简化版:
struct cfs_bandwidth {
raw_spinlock_t lock; /* 0 4 */
+ u8 idle; /* 4 1 */
+ u8 period_active; /* 5 1 */
+ u8 slack_started; /* 6 1 */
ktime_t period; /* 8 8 */
- u8 idle;
- u8 period_active;
- u8 slack_started;
u64 quota;
u64 runtime;
u64 burst;
u64 runtime_snap;
s64 hierarchical_quota;
struct hrtimer period_timer;
struct hrtimer slack_timer;
struct list_head throttled_cfs_rq;
int nr_periods;
int nr_throttled;
int nr_burst;
u64 throttled_time;
u64 burst_time;
};
副作用:period_timer 的偏移从 64 变成 56,跨越了 cacheline 0/1 边界——之前独占 cacheline 1。patch 描述里也承认这一点,并解释 period_timer 本身就要访问多个 cacheline,不是真正关键的热路径。
+--- old layout (240 B, holes=13) --------------------+
| offset: 0 4 8 16 24 32 40 48 56 64 |
| field: LK PAD PR QU RT BU RS HQ [3u8] PAD |
+--- cacheline 0 (0-63) ------------------------------+
| offset: 64 128 |
| field: PTIMER STIMER |
+--- cacheline 1/2 (64-127 / 128-191) ----------------+
| offset: 192 208 212 216 224 232 |
| field: TCRQ NR_PER NR_THR NR_BUR PAD T_TIME B_TIME|
+--- cacheline 3 (192-255) ---- size 240 -------------+
+--- new layout (232 B, holes=5) ----------------------+
| offset: 0 4 5 6 7 8 16 24 32 40 48 56 |
| field: LK U8*3 PAD PR QU RT BU RS HQ PTIMER_head8 |
+--- cacheline 0 (0-63) ---- PTIMER straddles line --+
| offset: 64 120 |
| field: PTIMER_tail56 STIMER_head8 |
+--- cacheline 1 (64-127) ---- STIMER straddles line -+
| offset: 128 184 |
| field: STIMER_tail56 TCRQ_head8 |
+--- cacheline 2 (128-191) ---- TCRQ straddles line --+
| offset: 192 200 204 208 216 224 |
| field: TCRQ_tail8 NR_*3 PAD T_TIME B_TIME |
+--- cacheline 3 (192-231) -- size 232 ---------------+
类比
把行李箱回程打包:原本几件小东西(u8 标志位)散落在大物件之间的空位;现在把它们塞到大件行李四周的缝隙里,整箱尺寸就能压一档。
或者,把抽屉整理:原抽屉里有几片 5 mm、4 mm 小隔板留着空隙没放任何东西;现在把小工具贴边塞进那些缝隙,抽屉总尺寸就能缩小一档。
Highlight:风险与注意点
- 缓存行分裂:
period_timer偏移由 64 变 56,跨越 cacheline 0/1 的 8 字节对不齐;slack_timer也类似横跨 cacheline 1/2。作者评估它本身就要触碰很多 cacheline、不是真正热路径,但实测仍是观察重点。 - 标题拼写错误: 系列标题里的
cfs_bandwith漏了d,正确写法是cfs_bandwidth。v2 没修这个,希望上游维护者/社区能宽容。 - 缺少编译期断言: 没有
BUILD_BUG_ON(sizeof(struct cfs_bandwidth) != 232);后续若添加新字段,可能再次引入 padding。建议加一条断言锁死布局。 - 依赖编译器/pahole 版本: 这种 layout 优化对 gcc/clang 的字段对齐行为与
pahole的输出格式很敏感,未来如果调整pahole选项或换编译器,需要重新核对。
版本变化
v1 → v2 仅调整了 commit message 措辞;代码层一行未动。这说明作者已与 reviewer 对齐(拿到 Reviewed-by from Ben Segall),纯文字纠错后再次发送。
一句话总结
把 cfs_bandwidth 末端的 3 个 u8 标志位搬到 lock 之后,压缩 padding,把结构体从 240 字节瘦到 232 字节。