Ben Andrew builds a small allocator step by step to show what happens between malloc and the kernel. The experiments explain why a successful free may leave a process’s memory footprint unchanged. The trade is visible in syscall traces, page faults, and timing results.

  • Batch kernel requests. A thousand 16-byte allocations make only two brk calls in his example. On his machine, malloc(16) takes 26.9 ns versus 610.1 ns for sbrk(16).
  • Reuse chunks. Headers record sizes, while a free list splits available space and merges adjacent blocks. A simple list works, but scanning it on every operation gets expensive.
  • Distinguish addresses from RAM. Reserving 1 GiB of virtual space leaves resident memory almost unchanged until the program writes to its pages.
  • Release only what can move. A live chunk near the heap’s top prevents a lower freed chunk from being returned by shrinking the heap. A separate mmap allocation can be unmapped independently.
  • Pay for glibc’s speed. Its size-class bins and thread-local cache make requests quick. In the author’s mixed-size benchmark, glibc averages 0.0789 µs per step but retains 19.0 MiB after every block is freed. Disabling that cache reduces retained memory to 0.250 MiB and slows steps to 0.121 µs.

free returns a block to the allocator’s control, not necessarily to the operating system. Andrew’s tuning runs make the latency-versus-memory trade concrete; the numbers belong to his particular workload and machine.