Memory allocator simulator

Every malloc has to find a free piece of the heap, and every free leaves a hole. Replay the same programs through five allocation strategies on a 64 KiB heap and watch where memory goes: rounded up inside blocks, or lost between them.

Free
64.0 KiB
Largest free block
64.0 KiB
Failed requests
0
Allocator
Program
By hand
Fill the heap with 300-byte objects, free every second one, then ask for four 2 KB blocks.

step 0 of 274

free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0free, 65536 bytes at 0

bytes the program asked forrounding, headersfreeeach row is 8 KiB

First fit
Live objects
0
Bytes asked for
0
Bytes taken
0
Internal waste
0
Free blocks
1
External fragmentation
0%
Free blocks examined per malloc
–

External fragmentation is 1 − largest free block ÷ total free: 0% when all free memory is one block.

  1. –Run the program or call malloc by hand.

How to use the simulator

The heap is drawn as eight rows of 8 KiB. Dark segments are the bytes a program asked for, light segments the extra each allocator took (headers and rounding), and blank space is free. Pick an allocator and a program, then run it, or call malloc and free yourself.

  • Run Free every other block with first fit: half the heap ends up free, yet a 2 KB request fails because no single hole is big enough.
  • Run the same program with Buddy: every 300-byte request takes 512 bytes, and the heap is full before the program has allocated everything.
  • Run Random churn with next fit and then best fit, and compare the external fragmentation and the number of blocks examined per request.

What is a memory allocator?

A memory allocator manages a program's heap: it hands out blocks of memory on request and takes them back when the program frees them. It cannot move a block once handed out, because the program holds pointers into it, and it cannot know what will be requested or freed next. Every design is a trade-off between time spent finding a block, memory wasted by rounding, and memory wasted in holes between live blocks.

The allocators on this page follow the common designs: free lists searched by first, next or best fit with immediate coalescing, the binary buddy system, and segregated size classes.

First, next and best fit

A free-list allocator keeps the free blocks and searches them for one big enough. First fit takes the first in address order. Next fit continues from where the previous search stopped, which is faster but spreads objects over the whole heap. Best fit takes the smallest block that is big enough, leaving the largest blocks intact. The chosen block is split if the leftover can hold a minimal block, and on free the block is merged with free neighbours, which a header and footer on each block (boundary tags) make an O(1) check. Each block here costs an 8-byte header and is rounded to 16 bytes, at least 32, as in glibc.

ProgramFirst fitNext fitBest fitBuddySize classes
Free every other block: failed requests111563
blocks examined per malloc3.01.53.03.815.0
peak heap used56.3 KiB56.3 KiB56.3 KiB64.0 KiB64.0 KiB
external fragmentation at the end94%94%94%98%0%
Random churn: failed requests00000
blocks examined per malloc9.61.011.316.61.4
peak heap used20.4 KiB20.2 KiB20.5 KiB28.1 KiB44.0 KiB
external fragmentation at the end10%87%8%18%23%
Requests and a cache: failed requests00000
blocks examined per malloc5.41.26.56.30.8
peak heap used16.9 KiB16.9 KiB16.9 KiB23.0 KiB34.0 KiB
external fragmentation at the end4%95%4%29%6%

Wilson, Johnstone, Neely and Boles reached the same ranking in their 1995 survey using traces of real programs: best fit and address-ordered first fit waste little memory, while next fit, despite its speed, fragments badly.

Fragmentation

Internal fragmentation is memory inside allocated blocks that the program did not ask for: headers, alignment and rounding to a size the allocator can manage. External fragmentation is free memory broken into pieces too small to use. In the first program, the free-list allocators had 30,544 bytes free when a 2 KB request failed, in holes of at most 1,744 bytes, a heap that is about half empty and still full.

Donald Knuth observed that a heap in a steady state carries a predictable number of holes. If each allocation splits a free block with probability p, the number of free blocks settles at about p/2 times the number of allocated blocks, the "fifty-percent rule". The simulator reproduces it for random requests of 16 to 416 bytes:

AllocatorLive objectsFree ÷ allocated blocksp / 2
First fit600.3650.351
First fit1500.3120.311
Next fit600.5140.499
Next fit1500.4810.477
Best fit600.2710.261
Best fit1500.2050.202

The buddy system

Kenneth Knowlton's buddy system of 1965 manages blocks whose sizes are powers of two, each aligned to its size. A request takes the smallest power of two that fits; a larger block is split in half repeatedly to make one, and each split produces two buddies. A freed block merges with its buddy if the buddy is free, and the buddy's address is the block's address with one bit flipped, so finding it costs nothing. Merging is complete: once everything is freed, the heap is one block again.

The cost is internal fragmentation. A 300-byte object plus header needs 308 bytes and gets 512, so in the first program the buddy allocator held only 128 of the 180 objects and wasted 13.3 KiB in rounding. The Linux kernel uses a buddy allocator for physical pages, where requests are already multiples of the page size.

Size classes and slabs

Segregated allocators round each small request up to one of a few size classes and keep separate pages for each class; here 14 classes from 16 to 512 bytes in 2 KiB pages. Objects of one class are packed without headers, finding a slot is quick, and freeing one leaves a hole exactly one object wide, which the next request of that class fills. Jeff Bonwick's slab allocator for the SunOS kernel (1994) added caching of constructed objects; jemalloc, tcmalloc and mimalloc all use size classes for small objects, with per-thread caches on top.

The weakness is memory stranded in partly used pages: a page of 320-byte slots with one live object cannot serve a 64-byte request. In the first program, freeing every other object left every page half full, so no page could be returned and the 2 KB requests failed.

Allocators in practice

Production allocators combine these ideas. glibc's malloc keeps bins of free chunks by size, with fast per-thread caches (tcache) for small requests and mmap for very large ones. jemalloc and tcmalloc use size classes for small objects, page runs for larger ones and per-thread or per-CPU caches to avoid locks. Programs that allocate many objects of one type often add their own pools or arenas, which free everything at once. Garbage-collected runtimes can go further and compact the heap, moving objects to close the holes, something malloc can never do.

History

Kenneth Knowlton described the buddy system in "A fast storage allocator" in 1965. Donald Knuth's The Art of Computer Programming, volume 1 (1968), analyzed first fit, best fit, boundary tags and the buddy system, and stated the fifty-percent rule. Jeff Bonwick presented the slab allocator at USENIX in 1994, and Paul Wilson and colleagues' 1995 survey "Dynamic storage allocation: a survey and critical review" re-evaluated the classic policies on real program behavior. Doug Lea's dlmalloc, the basis of glibc's malloc, dates from 1987; jemalloc appeared in FreeBSD in 2005 and tcmalloc at Google around the same time.

Memory allocator questions and answers

What is external fragmentation?

Free memory split into pieces too small for the request at hand. In this page’s first workload the heap has 30,544 bytes free when a 2 KB request fails, because the largest free block is only 1,744 bytes. Coalescing adjacent free blocks reduces it, but cannot fix holes separated by live objects.

What is internal fragmentation?

Space handed out but not asked for, because the allocator rounds requests up: to a multiple of 16 bytes plus a header in malloc, to a power of two in a buddy allocator, to the next size class in a slab allocator. A buddy allocator turns a 300-byte request into a 512-byte block, which is why it ran out after 128 such objects in the first workload while the free-list allocators placed all 180.

Is best fit better than first fit?

Usually by a little, at the cost of searching more. On the random workload here, best fit ended with 8% external fragmentation against 10% for first fit, and examined 11.3 free blocks per request against 9.6. Next fit searches least (1.0) but scatters objects and fragments the heap worst (87%). Wilson and colleagues’ 1995 survey reached the same conclusion on real program traces: best fit and address-ordered first fit both do well, next fit does not.

How does the buddy system work?

Memory is managed in blocks whose sizes are powers of two, each aligned to its size. A request takes the smallest block that fits, splitting a larger one in half repeatedly if needed; the two halves are buddies. When a block is freed and its buddy is also free, they merge back, which is cheap because a block’s buddy is found by flipping one bit of its address. The cost is internal fragmentation of up to almost half of each block. The Linux kernel uses a buddy allocator for physical pages.

What is a slab allocator?

An allocator that keeps separate pages, or slabs, for each object size, so objects of one size are packed together with no headers and freeing one leaves a hole only that size can fill. Jeff Bonwick designed it for the SunOS kernel in 1994; Linux’s SLUB and user-space allocators such as jemalloc and tcmalloc use the same idea with size classes. The price is memory held in partly used pages, which only one size class can use.

What is the fifty-percent rule?

An observation by Donald Knuth: when a heap reaches a steady state of random allocations and frees, the number of free blocks settles at about half the number of allocated blocks, scaled by how often an allocation splits a block (p/2 of them). So a heap with 1,000 live objects carries roughly 500 holes, which is why fragmentation never goes away on its own.

Further reading

Published Updated