The layout illustrates how malloc's heap sits among other memory regions, with large unmapped gaps.
*Malloc offers a simple pointer, but the kernel dance is hidden deep in glibc. The article dissects the syscalls, heap layout and why a custom bump allocator falls short.*
The malloc interface looks innocent: ask for a size, get a pointer, free when done. Behind that veneer, glibc orchestrates a sophisticated dance with the kernel, pulling in memory in bulk and slicing it in user space. A benchmark that loops a thousand malloc(16) calls reveals only two brk syscalls, the first to locate the current break and the second to extend the heap by 132 KiB. The remaining calls are satisfied from that slab, avoiding the 575 ns penalty of a raw syscall. Understanding this mechanism is crucial for developers who assume each allocation incurs a kernel round‑trip.
A call to malloc(16) returns a void pointer to a contiguous block. In a test that called malloc(16) a thousand times, strace shows only two brk syscalls: one to query the program break and one to grow the heap by 132 KiB. The remaining 999 allocations are satisfied from that pre‑reserved region. The kernel interaction is therefore batched, not per‑allocation. This batching explains why the user‑space allocator can hand out memory far faster than a raw syscall.
Syscalls cost roughly 575 ns for an empty call. Measuring a loop of a million calls, sbrk(16) adds only 35 ns to that baseline, while malloc(16) averages 26.9 ns per call. The ratio shows malloc runs about 22.7 times faster than sbrk. The speed comes from glibc keeping large chunks in user space and carving out the requested 16‑byte pieces without re‑entering the kernel. The result is a near‑instant pointer return for the majority of allocations.
A process address space contains text, data, bss, heap and stack regions. Running a small program prints addresses that rise from the binary code up to the stack. The heap address for malloc(16) sits well below the program break, which in one run lay 23.4 TiB away from the libc printf symbol—about 1,530 times the machine's 15.6 GiB of RAM. The gap is unmapped, so it consumes no physical memory. Each allocation also carries a header; chunk sizes are multiples of 16 and the header occupies 24 bytes before the returned pointer.
A toy bump allocator moves the program break on every request via sbrk. In a timing test, sbrk(16) costs 610.1 ns per call, far slower than malloc(16). Moreover, the bump design cannot reuse freed memory; a later allocation lands after earlier ones even if a middle block was freed. The example sequence a, b, c, free(b), d shows d placed after c, leaving a hole. Glibc's free list and chunk headers enable reuse, a capability the simple bump allocator lacks.
In a test of a thousand malloc(16) calls, only two brk syscalls were observed.
A raw syscall costs about 575 ns, while malloc(16) averages 26.9 ns per call.
The bump allocator uses sbrk for each request, costing 610.1 ns per call and cannot reuse freed memory, unlike glibc malloc.
The evidence is clear: glibc's malloc leverages bulk kernel allocations, internal headers and a free‑list to outpace raw sbrk calls by orders of magnitude. A naïve bump allocator cannot match this efficiency or reuse freed blocks. As software continues to demand rapid, fine‑grained memory management, the hidden engineering inside malloc remains a vital, if invisible, performance safeguard.
Sources: Hacker News