Skip to content

mm: scale kmalloc with bounded per-CPU magazines - #112

Open
JiTianYu391 wants to merge 1 commit into
ViudiraTech:developfrom
JiTianYu391:codex/perf-heap-magazines
Open

JiTianYu391 wants to merge 1 commit into
ViudiraTech:developfrom
JiTianYu391:codex/perf-heap-magazines

Conversation

@JiTianYu391

Copy link
Copy Markdown
Contributor

malloc(64)+free 单核约 115 ns,五核同时约 2130 ns/op。此前锁采样中 cache->lock 有 70% 获取遇占用;free 的 pointer_owner 还逐次获取全局 page_lock。

为 17 个 kmalloc 尺寸类添加每 CPU magazine(每类至多 32 个对象/16 KiB),批量补充/归还;usage 计数也分散到每 CPU。有效分配块的 buddy tag 在其存活期间稳定,pointer_owner 改为原子读取 tag。保留对象 cookie、毒化和 ALLOCATED→CACHED 的 CAS,缓存对象继续计入 slab->inuse,防止 shrink 提前释放。动态 typed cache 不使用 magazine,保留 ctor/dtor 路径。

启动问题已先复现并用 qemu -gdb tcp::12345 / GDB 沿 RBP 定位:smp_init → aligned_alloc → malloc → heap_cpu_cache → get_current_cpu_id;cpu_count=5 而 cpus=NULL,原始 RIP 是 get_current_cpu_id+96,读取 cpus[0].lapic_id(地址 8);panic 又在 send_ipi_all 缺页,掩盖报告。给 CPU ID 的 BSP 早期回退补上 cpus 未分配的条件。

A/B:两边同一覆盖层和 SMP 早期回退,仅 allocator 不同;均 kbench=3,交替 5 对,先取每次启动的中位数,再取 5 次启动中位数。日志为 build/kbench/heap-verified-{A,B}-{1..5}.log。

项目(ns/op) A B 变化
malloc/64/solo 115.4 99.4 -13.9%,不作收益结论
malloc/64/all-cpus 2130.3 107.8 -94.9%
malloc/4096/solo 171.5 159.0 -7.3%,不作收益结论
malloc/4096/all-cpus 2992.1 184.1 -93.8%
malloc/65536/solo 167.5 145.1 -13.4%,不作收益结论
malloc/65536/all-cpus 2793.1 2382.0 -14.7%,不作收益结论
yield/solo 92.0 92.5 +0.5%,噪声范围

10 次启动、30 轮均完成,无 panic/stale wait。每次启动的临时完整性检查覆盖全部 17 类、96 对象批次和溢出、数据校验、realloc、动态 cache busy/shrink/destroy、free 毒化、heap_validate;均 0 失败,且恰好收到一次预期的重复释放错误。检查代码留在被忽略的测试目录,没有进入 PR。

参考 Linux per-CPU 对象缓存:缓存中的对象使所属 slab 保持占用。缓存有固定上限;本 PR 不引入 CPU 热拔插或内存压力回收机制。

验证环境:5 vCPU、KVM、2 GiB、每次 QEMU 超时 60 秒。所有独立分支直接从 origin/develop@153578b5 开出,目标均为 develop;每个分支均独立完成 GCC 16.2 / LTO 构建,构建源只取 git ls-files '*.c',切分支后清除对应 .o/.d。按已知基线问题临时关闭 CONFIG_VIRTIO_GPU。除 strcpy PR 自身外,独立构建在链接命令临时加 -Wl,-u,strcpy,绕过原始 develop 的已复现 LTO 缺陷;没有把其他 PR 的源码带进分支。

运行基准时明确使用独立 sched 修复、strcpy 修复、kbench 和 kthread 绑定的共同测试覆盖层,以免把已知调度崩溃或实际 CPU 不符误算成待测修改的效果。覆盖层仅用于测试镜像,不构成提交依赖。

已否决方案:把偷取限制为源 rq 至少两个任务,曾使 ping-pong 约 3 µs 恶化到 7.8 µs;轮询 idle 无明显收益。这些改动均未采用。宿主负载高,仅把交替多次中位数中超过 20% 的差异视为性能结论。

🤖 Generated with ClaudeCode

On a 5-vCPU KVM guest, malloc(64)+free scales from about 115 ns alone to
2,130 ns with five workers. Prior contention sampling found 70% of slab
cache lock acquisitions contended; pointer_owner also took heap.page_lock
on every free. Reproduce with kbench=3 and five alternating A/B boot pairs.

Give each of the 17 kmalloc size classes a per-CPU magazine capped at
32 objects / 16 KiB. Refill and spill in batches under the slab cache lock,
keep cached objects counted in slab->inuse so shrink cannot reclaim them,
and retain a CAS transition from ALLOCATED to CACHED to reject double free.
Typed/dynamic caches keep their existing constructor/destructor path.
Use stable buddy ownership tags for valid live pointers without taking the
page lock, and aggregate usage counters from the per-CPU front ends.

The new early malloc caller also exposed smp_init publishing cpu_count
before allocating cpus. GDB found smp_init -> aligned_alloc -> malloc ->
heap_cpu_cache -> get_current_cpu_id with cpu_count=5 and cpus=NULL, faulting
at cpus[0].lapic_id (address 8); panic then faults in send_ipi_all. Extend
the BSP fallback until the CPU table exists.

Validation: five alternating A/B boot pairs, three rounds per boot,
median within each boot then median across boots (ns/op):
                     A         B
  malloc/64/solo      115.4      99.4
  malloc/64/all-cpus 2130.3     107.8
  malloc/4096/solo    171.5     159.0
  malloc/4096/all    2992.1     184.1
  malloc/65536/solo   167.5     145.1
  malloc/65536/all   2793.1    2382.0
  yield/solo          92.0      92.5
Only the >20% changes (64 B and 4 KiB multi-CPU) establish a performance
gain; smaller differences are treated as noise. All ten boots complete.
Every boot passes heap_validate, all size classes with batch spills,
realloc data checks, dynamic slab busy/shrink/destroy, free poisoning and
exactly one expected duplicate-free rejection, with zero failures.

The comparison images use the same scheduler/kbench/pinning/strcpy test
overlays and SMP bootstrap guard; only the allocator differs. Temporary
integrity checks remain outside this PR. Standalone origin/develop build
uses CONFIG_VIRTIO_GPU off and -Wl,-u,strcpy for the existing LTO defect.
The rejected two-task steal minimum and idle polling are not included.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant