CS 208 s22 — Dynamic Memory Allocation: Free Lists
Table of Contents
1 Introduction
Implementation questions:
- freeing: how much do we free given just a pointer?
- free block organization: how to keep track of them
- placement: which free block should we choose to fulfill a request
- splitting: what do we do with the remainder of a free block after part of it is allocated
- coalescing: what do we do when a block is freed
2 Implicit Free List
- since block size will always be a multiple of 16 due to alignment, the four lower order bits of the size will always be 0
- we can make efficient use of the header by using these four bits to indicate if the block is allocated or free
- the block size includes the payload and any padding, and is thus an implicit pointer to the start of the next block
- we will need some special block to mark the end of the free list (e.g, allocated bit, size 0)
- implicit free list is simple, but operations are linear in the number of blocks since we have to traverse the list
- alignment combined with block format impose a minimum block size
- https://docs.google.com/spreadsheets/d/1Tj8LTKBBqLodX5K8GbqCVUSWOFur6KzXZshQhx0bF4E/edit?usp=sharing#gid=858123926
- https://docs.google.com/spreadsheets/d/1Tj8LTKBBqLodX5K8GbqCVUSWOFur6KzXZshQhx0bF4E/edit?usp=sharing#gid=1764373338
2.1 Placing Allocated Blocks
- an allocator's placement policy determines the choice of free block to fulfill allocation
- first fit: take the first free block that's big enough
- end up with lots of small "splinters" near the start of the list, large blocks near the end
- next fit: like first fit, but start search where the previous one left off
- unclear if this is better than first fit in practice
- best fit: choose the smallest free block that fits
- better memory utilization, but requires exhaustive search
- first fit: take the first free block that's big enough
- if the fit is not good, an allocator might opt to split the free block into two parts
- if necessary, an allocate will ask the kernel for additional heap memory, inserting a new free block into the list
2.2 Coalescing Free Blocks
- adjacent free blocks cause false fragmentation—free memory chopped up into many small unusable free blocks
- to address this an allocator must merge (coalesce) adjacent free blocks
- immediate coalescing: merge blocks whenever one is freed
- simple, constant time
- can lead to a form of thrashing where a block is coaslesced and split repeatedly
- deferred coalescing: wait and coaslesce at a later time (e.g., scan the hope when a request fails)
- often the more efficient choice
- immediate coalescing: merge blocks whenever one is freed
2.2.1 Boundary Tags
- with a simple header containing the block size, we can reach the next block, but what about the previous block?
- we'd have to traverse the free list until we reached the current block
- instead, add a footer to each block that's the same as the header
- together, the header and footer form boundary tags
- if we go back one word from the header, we get to the footer of the previous block
- four cases for coalescing:
- previous and next blocks are both allocated
- previous block is allocated, next block is free
- previous block is free, next block is allocated
- previous and next blocks are both free
- https://docs.google.com/spreadsheets/d/1Tj8LTKBBqLodX5K8GbqCVUSWOFur6KzXZshQhx0bF4E/edit?usp=sharing#gid=941454649
- boundary tags can introduce significant memory overhead in the case of many small blocks
- fortunately, we only need to have footers in free blocks, since we only care about the size of the previous block when it is free and we want to merge it
- we still need a way to tell if the previous block is allocated, so we'll add that information to one of the unused low-order bits of the header
3 Explicit Free Lists
- organize free list as a doubly-linked list using the payload of free blocks (by definition unused) to store the pointers
- now placement is linear in the number of free blocks instead of the total number of blocks
- assuming a first-fit policy:
- if list uses last-in first-out by inserting new free blocks at the head, freeing a block is constant time
- better memory utilization can be achieved if free blocks are maintained in address order
- minimum block size must increase to accomodate list pointers, potentially increasing internal fragmentation
- splitting, boundary tags, coalescing are general to all allocators
- free blocks still coalesce with those adjacent in memory
- remove from list, reinsert at the head
- list can hold blocks in any order, regardless of memory address
- free blocks still coalesce with those adjacent in memory
4 Quick Checks
Which allocation strategy and requests remove external fragmentation in this heap?1 B3 was the last fulfilled request.
- best-fit: malloc(50), malloc(50)
- first-fit: malloc(50), malloc(30)
- next-fit: malloc(30), malloc(50)
- next-fit: malloc(50), malloc(30)
What is the relationship between throughput and memory utilization?2
5 Implicit Free List Implementation
- section 9.9.12 walks through an implementation of an implicit free list
- beware it is in 32-bit, and your will need to be in 64-bit (generally involves changing constants)
- pay more attention to the text descriptions than the code
- use macros or helper functions like these for pointer manipulation
- make sure you understand why these work/are necessary before you start coding
/* Round up size to the nearest multiple of 16 */ /* Basic constants and macros */ #define WSIZE 8 /* word size (bytes) */ #define DSIZE 16 /* doubleword size (bytes) */ #define CHUNKSIZE (1<<12) /* initial heap size (bytes) */ #define OVERHEAD 16 /* overhead of header and footer (bytes) */ /* NOTE: feel free to replace these macros with helper functions and/or * add new ones that will be useful for you. Just make sure you think * carefully about why these work the way they do */ /* Pack a size and allocated bit into a word */ #define PACK(size, alloc) ((size) | (alloc)) /* Read and write a word at address p */ #define GET(p) (*(size_t *)(p)) #define PUT(p, val) (*(size_t *)(p) = (val)) /* Perform unscaled pointer arithmetic */ #define PADD(p, val) ((char *)(p) + (val)) #define PSUB(p, val) ((char *)(p) - (val)) /* Read the size and allocated fields from address p */ #define GET_SIZE(p) (GET(p) & ~0xf) #define GET_ALLOC(p) (GET(p) & 0x1) /* Given block ptr bp, compute address of its header and footer */ #define HDRP(bp) (PSUB(bp, WSIZE)) #define FTRP(bp) (PADD(bp, GET_SIZE(HDRP(bp)) - DSIZE)) /* Given block ptr bp, compute address of next and previous blocks */ #define NEXT_BLKP(bp) (PADD(bp, GET_SIZE(HDRP(bp)))) #define PREV_BLKP(bp) (PSUB(bp, GET_SIZE((PSUB(bp, DSIZE)))))
6 Summary
- Allocator Policies
- All policies offer trade-offs in fragmentation and throughput.
- Placement policy:
- First-fit, next-fit, best-fit
- Splitting policy:
- Always? Sometimes?
- Be mindful of minimum block size—never split off a block smaller than the minimum
- Which side of the split should the allocated block be on?
- Left? Right? Alternate?
- Coalescing policy:
- Immediate vs. deferred
7 Practice
CSPP practice problem 9.7 (p. 852, 854)
Determine minumum block size for the following combinations of alignment requirements and block formats. Assumptions: implicit free list, zero-size payloads are not allowed, and headers and footers are stored in 4-byte words.3
Alignment | Allocated block | Free block | Minimum block size (bytes) |
---|---|---|---|
Single word | Header and footer | Header and footer | |
Single word | Header, but no footer | Header and footer | |
Double word | Header and footer | Header and footer | |
Double word | Header, but no footer | Header and footer |
Footnotes:
2. first-fit: malloc(50), malloc(30) will fill in the gaps between allocated blocks, removing external fragmentation. (1) will leave a gap of 30 between B3 and B2, (3) will leave the gap of 50 between B1 and B3, and (4) will leave a gap of 20 between B1 and B3 and a gap of 30 between B3 and B2
There's a trade-off between processing a request faster and finding a better choice of block
Alignment | Allocated block | Free block | Minimum block size (bytes) |
---|---|---|---|
Single word | Header and footer | Header and footer | 12 |
Single word | Header, but no footer | Header and footer | 8 |
Double word | Header and footer | Header and footer | 16 |
Double word | Header, but no footer | Header and footer | 8 |