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

implicitblock.png

implicit-free-list.png

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
  • 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

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

boundary-tags.png

  • four cases for coalescing:
    1. previous and next blocks are both allocated
    2. previous block is allocated, next block is free
    3. previous block is free, next block is allocated
    4. 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

explicit-block.png

explicit-free-list.png

  • 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

4 Quick Checks

Which allocation strategy and requests remove external fragmentation in this heap?1 B3 was the last fulfilled request.

poll-heap.png

  1. best-fit: malloc(50), malloc(50)
  2. first-fit: malloc(50), malloc(30)
  3. next-fit: malloc(30), malloc(50)
  4. 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:

1

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

2

There's a trade-off between processing a request faster and finding a better choice of block

3
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