0/1 已展开

LLM 分析

CFS Bandwidth:紧凑结构体布局以节省内存

系列概况

  • 标题: [PATCH RESEND v2] sched: adjust the layout of the cfs_bandwith structure to save memory
  • 作者: zenghongling zenghongling@kylinos.cn
  • 版本: v2(RESEND,单 patch)
  • 规模: 1 个文件,3 处插入、3 处删除
  • 修改文件: kernel/sched/sched.h(第 444–457 行附近 struct cfs_bandwidth
  • Message-ID: 20260320072028.41409-1-zenghongling@kylinos.cn
  • 完整性: 完整 —— diff 块、Before/After 的 pahole 输出、Reviewed-by、Signed-off-by、Changes in v2 都齐全

补丁目的

利用 pahole 把 struct cfs_bandwidth 里散落的 3 个 u8 状态位(idleperiod_activeslack_started)紧贴到 raw_spinlock_t lock 后面,借助这些字段后面的 8 字节对齐 hole 顺带把它们"吃掉"。

最终效果:

  • 结构体总大小从 240 字节降到 232 字节
  • hole 字节数从 13 降到 5
  • 最后一个 cacheline 利用率从 48/64 提升到 40/64

唯一权衡:period_timer(64 字节)现在从 offset 56 开始,正好骑在 64 字节 cacheline 边界上,访问它可能多触发一次 cacheline load。作者自称这条路径本就涉及多个 cacheline 且非热路径,所以可接受。

旧流程的问题

原结构体里同时存在两类"塞不满"的缝隙:

  1. lock(4 字节)后面留 4 字节 hole,才能对齐到 8 字节的 period
  2. 三个 u8 状态位加一块只占 3 字节,再后面 u64 throttled_time 需要 8 字节对齐,中间留下 5 字节 hole。
  3. int nr_burst 之后还有一处 4 字节 hole,给后面对齐的 u64 throttled_time

pahole 报告:sum holes = 13 字节、padding = 8 字节、4 个 cacheline、最后一个 cacheline 只用 48/64 字节。

新流程

Before (240 B)                       After (232 B)
+----------------------+             +----------------------+
| lock          [0..3] |             | lock          [0..3] |
| hole 4..7      (4B)  |             | idle           [4]   |
| period        [8..15]|             | period_active  [5]   |
| ...                 |             | slack_started  [6]   |
| hier_quota   [48..55]|             | hole        7  (1B)  |
| idle [56]            |             | period       [8..15] |
| period_active [57]   |             | ...                   |
| slack_started [58]   |             | period_timer[56..119]|<-- cross cacheline @64
| hole 59..63   (5B)   |             | slack_timer [120..183]|
| period_timer[64..127]|             | throttled_cfs_rq     |
| slack_timer [128..191]|            |   [184..199]         |
| throttled_cfs_rq     |             | nr_*           [200..211]|
|    [192..207]        |             | hole          (4B)   |
| nr_*       [208..219]|             | throttled_time[216..223]
| hole          (4B)   |             | burst_time    [224..231]
| throttled_time[224..231]            +----------------------+
| burst_time   [232..239]            CL4 used: 40 / 64 B
+----------------------+
CL4 used: 48 / 64 B

关键实现

改动只在 include 段,没有运行时新增逻辑。语义、并发模型、throttle 行为都保持不变。

struct cfs_bandwidth {
#ifdef CONFIG_CFS_BANDWIDTH
    raw_spinlock_t lock;
    u8 idle;             /* moved up: 紧跟 lock */
    u8 period_active;    /* moved up */
    u8 slack_started;    /* moved up */
    ktime_t period;
    u64 quota;
    u64 runtime;
    u64 runtime_snap;
    s64 hierarchical_quota;
    struct hrtimer period_timer;  /* offset 56, crosses cacheline */
    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;
};

唯一变化:把 idle; period_active; slack_started 三行从 hierarchical_quota 之后搬到了 lock 之后,并删去原位置。

类比

把 struct 想成一个 4 格的行李箱,每格 64 字节。原本 4 个"小件"(1 个 lock + 3 个 u8)分散在第 1 格和第 2 格开头,每段后面又紧跟一件"必须 8 字节整齐摆的大家具(u64 / hrtimer)",于是每段都留下一截塞不满的缝隙。补丁相当于把这 4 个小件拢成一摞塞进第 1 格开头,第一道缝隙完全消失,整体少用了 8 字节。

唯一代价是:period_timer 这件 64 字节的大家具现在正好骑在第 1 格和第 2 格的格挡上,需要时偶尔要跨格伸手够一次。但这条路径本来就在多格里翻东西,多翻一次也没关系。

Highlight:风险与注意点

  1. 跨 cacheline 的微性能:访问 period_timer 现在可能比之前多一次 cacheline load。建议用 perf stat -e cache-misses 在 throttle 密集触发场景(如 hackbench --pipe 高负载)里做对照。
  2. 正确性:字段重排不改变并发语义。pahole 报告里 forced holes 从 1 降到 0,说明此处没有强加 alignment 要求,packing 是干净的。
  3. v2 只是修文案Changes in v2: - fix the commit message. 表示 v2 没补跑 perf 数据,也没有新测试,仅 commit message 措辞改动。
  4. 下游 offset 依赖cfs_bandwidth 没有 EXPORT_SYMBOL,但 BTF / vmlinux 里的字段 offset 会变;任何 CO-RE BPF 程序若使用 offsetof 读这些字段,需重新生成 vmlinux.h。
  5. Reviewed-by 分量Reviewed-by: Ben Segall(Google CFS bandwidth 维护者之一)显著降低正确性顾虑,但他未对"跨 cacheline 是否真的无影响"做正面背书。

版本变化

  • v2(RESEND):diff 与 v1 完全一致,仅 commit message 措辞修复;phole 输出作为直观证据保留在邮件正文里。
  • v1 → v2 没有功能或性能改动,仅提交说明改写。

一句话总结

把 3 个 u8 状态位塞到 lock 后面,借自然对齐 hole 吃掉冗余 padding,让 cfs_bandwidth 从 240 字节瘦到 232 字节,代价是 period_timer 跨 cacheline,但 throttle 本就非热路径。