背景

SLUB 在 per-CPU 缓存的对象不够时,会从 partial slab 给 per-CPU 缓存补充对象(refill)。一次 refill 往往用不完一个 slab 的对象,剩下的要"返回"该 slab:把剩余对象接回 slab 的 freelist 尾部。原路径为此要遍历 slab 的 freelist 找到尾,再把 slab->freelist 接上去。

┌────────────────────────────────┐
│ refill 后把剩余对象返回该 slab │
└───────────────┬────────────────┘
                ▼
┌────────────────────────────────┐
│ 原路径遍历 slab 的空闲链表找尾 │  这条链表可能是 cache cold 的
└───────────────┬────────────────┘
                ▼
┌────────────────────────────────┐
│ 遍历冷链表带来不必要的缓存开销 │
└────────────────────────────────┘

问题在于,这条 freelist 很可能是 cache cold 的。refill 操作本身没怎么碰它,遍历它意味着把一串冷缓存行依次拉进 CPU。而实际上,refill 刚对这个 slab 调用过 get_freelist_nofreeze 取走 freelist,这期间大概率没有别的 CPU 向它释放对象,freelist 的尾就是上次留下的样子。

问题

  • refill 返回剩余对象要遍历 slab freelist 找尾
  • 这条 freelist 常是 cache cold 的
  • 遍历冷链表把冷缓存行依次拉进 CPU
  • refill 刚取过 freelist,大概率无人改动,遍历纯属多余

方案

核心是给这条返回路径加一个乐观 fast path。

既然该 slab 刚被 get_freelist_nofreeze 取走 freelist、期间大概率没人改过,就直接把剩余 freelist 整段 reattach 回去,跳过找尾的遍历。新增的 __slab_try_return_freelist 做的就是这件事。

旧:遍历空闲链表找尾再接回
┌────────────────────────┐
│ 遍历 slab 空闲链表到尾 │
└───────────┬────────────┘
            ▼
┌────────────────────────┐
│   把剩余对象接在尾部   │
└────────────────────────┘

新:乐观整段接回,免遍历
┌──────────────────────────────────────┐
│ 该 slab 刚取过空闲链表,大概率无人改 │
│ 动                                   │
└──────────────────┬───────────────────┘
                   ▼
┌──────────────────────────────────────┐  省去遍历 cache cold 链表;slab 放回 partial
│       直接整段接回剩余空闲链表       │  链表尾部以匹配释放路径
└──────────────────────────────────────┘

乐观路径还顺带简化了 slab 的去向:既然没遍历、没 detach freelist,__refill_objects_node 里本就有的逻辑会负责把这种 slab 放回 partial 链表,所以新函数只需把 slab 放回 per-CPU 的 pc.slabs 列表,不必再单独处理 partial;原先"列表大概率为空"的 unlikely 注解也随之移除。放回 partial 链表时要加到尾部而非头部,以匹配 __slab_free 的释放顺序:加头部会引入性能回归,加尾部则消除。这也会影响少数"从 partial 抓取却一点没 refill"的 slab,但那种情况比 partial refill 少见得多。配套简化是,删掉返回空 slab 时那道极不可能触发的 min_partial 限额检查。

封面信将本系列定位为无副作用的返回路径优化,并为后续涉及取舍的改动(如 spilling 或 caching leftover)打下更干净的 baseline。

收益

作者未提供性能数据。从机制与代码逻辑推断的预期收益:

  • 返回路径跳过对 cache cold freelist 的遍历,省下冷缓存行回填,refill 热路径更省
  • 乐观 reattach 无需遍历、无需单独放回 partial,代码也更直接
  • 放回 partial 链表尾部匹配释放路径,保持整体无副作用