0/1 已展开

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 字节对齐边界开始,于是 lockperiod 之间留下 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 字节。