Master the fundamental concepts of cpython internals through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksMost \`malloc\` calls in CPython do not hit the system allocator directly. \`obmalloc\` in \`Objects/obmalloc.c\` layers pools, arenas, and size classes tuned for small, short-lived objects.
Allocation tiers:
Small object requests (typical \`PyObject\` headers plus payload) are served from a free list inside a pool, avoiding syscall overhead and reducing fragmentation.
For example, allocating thousands of identical small tuples reuses slots from the same pool rather than calling \`malloc\` each time.
Stats to watch: pool utilization, arena count, and fallback rate to the system allocator under memory pressure.
Falling through to system `malloc` for large allocations is intentional. Pool exhaustion under fragmentation is a sign workloads allocate many different sizes; watch arena growth in long-running servers.
This exercise asks you to document obmalloc's pool and arena design. You will explain why Python allocates small objects differently than a generic \`malloc\` and how that shapes interpreter performance.
You will use the same mental model here when reading production interpreter source later in the track. Sketch one concrete input on paper, predict the outcome, then confirm with code. That discipline catches logic errors early and makes debugging far faster when you extend the implementation in follow-on tasks.
Simulate obmalloc, CPython's small-object allocator (3.8 layout, 64-bit). Requests of 1..512 bytes are rounded up to a size class, a multiple of 16. They are served from 4 KiB pools, each dedicated to one class, carved from 256 KiB arenas of 64 pools. Everything else goes to the system malloc. Freed blocks return to their pool's free list, empty pools return to their arena, and a completely empty arena is released to the OS.
cLoading…
(size - 1) / 16, so the block size is (class + 1) × 16. Sizes 0 and over 512 go to the system malloc.(4096 - 48) / blocksize blocks. Pool p of arena a lives at 0x10000000 + a×0x40000 + p×0x1000, and block b lives at pool + 48 + b × blocksize. Freeing finds the pool from the address alone: addr & ~0xfff.cLoading…
[new arena], [new pool] or [reused pool] when it obtained a pool, and none otherwise.p0..p6: 7 x 512 bytes (class 31) (plus , N new arena(s)) and freed N block(s) named p*. Each arena released during them still prints arena N released to the system.arenas: L live, A allocated, R released. Then, for each live arena, arena N @ 0x...: U of 64 pools in use followed by class C (S bytes): P pool(s), U/T blocks used for each class present. It ends with system malloc: N block(s), B bytes.malloc N: name in usemalloc N: bad sizemalloc N: out of arenasfree N: not an allocated pointermalloc-many: bad argumentsInput:
cLoading…
Output:
cLoading…
Hidden tests cover a full pool going back to the front of usedpools, overflow into a second arena, reusing an emptied pool for another size class, arenas released after bulk frees, a 0-byte request, and name errors.