Lecture 011

Basic of Malloc

void *malloc(size_t size):

void free(void *p):

Other functions:

Programming Assignment

Simple Implementation

In mm-reference.c implement mm-malloc and mm_free

Explicit Allocators:

Performance

Aggregate payload: P_k

Current heap size: H_k

Overhead (after k+1 requests): O_k = H_k / (max_{i \leq k}) - 1.0

Utilization: \frac{\text{peak payload}}{\text{heap size}}

Simple Benchmark Visualization 10 blocks

Simple Benchmark Visualization 10 blocks

Fragmentation

Internal Fragmentation

External Fragmentation: fragmentation by discontinue allocation

Implementation

Tracking the Size

Header: store a word before allocated block to indicate block size (in bytes, including itself, payload, and padding)

Tracking the Next Free Block

Implicit list: use block size (header) and additional tag (indicate whether the block is allocated/free) as a linked-list (footer if double-linked list are used for coalesce), Not used in practice for malloc/free because of lineartime allocation.

Explicit list: store address of next free block in freed space

Segregated free list: multiple linked list to track difference size of free block

Blocks sorted by size: balanced tree (red-black tree) with pointers within each free block, and the length used as a key

Methods Summary

Methods Summary

Policy Summary

Placement policy:

Splitting policy:

Coalescing policy:

Segregated List (Seglist)

Seglist: create lists for each size classes free blocks (separate classes for small size, one class for larger size [2^i+1, 2^{i+1}])

Placement policy:

Freeing policy: coalesce and place on appropriate list

Explicit Free List

Explicit List:

Explicit Free List

Explicit Free List

Splitting Policy: maintain free block to where it was

Freeing Policy:

Coalescing Policy:

Implicit List

Implicit list:

Implicit list visualizations:

typedef uint64_t word_t;
typedef struct block {
  word_t header;
  unsigned char payload[0];
} block_t;

void *get_payload(block_t B) {
  return (void *)(B->payload);
}

block_t *get_block((void *) bp) {
  return (block_t *) ((unsigned char *) bp - offsetof(block_t, payload))
}

bool get_alloc(block_t B) {
  return B->header & 0x01;
}

uint64_t get_size(block_t B) {
  return B->header & ~0xfL;
}

block_t new_block(...) {
  ...
  block->header = size | 0x01;
}

static block_t *find_next(block_t *block) {
  return (block_t *) ((unsigned char *) block + get_size(block));
}

// @brief choose first free block that fits (11.9% overhead)
//        O(n_block) time
//        may cause fragments at the beginning
static block_t *first_fit(size_t asize) {
  block_t *block;
  for (block = heap_start; block != heap_end; block = find_next(block)) {
    if (!get_alloc(block) && asize <= get_size(block)) return block;
  }
  return NULL;
}

// @brief search starting where previous search finished (21.6% overhead)
//        faster than [first_fit] to avoid re-scanning
//        but worse fragmentation
static block_t *next_fit(size_t asize) {

}

// @brief search entire list, choose best free block (8.3% overhead)
//        with fewest byte left over
//        improve memory utilization
//        slower than [first_fit]
//        greedy algorithm, not guarantee optimality
static block_t *best_fit(size_t asize) {

}

// @brief perfect fit, not achivable (1.6% overhead)
static block_t *best_fit(size_t asize) {

}

// @WARNING: incomplete code
void write_header(block_t *block, size_t asize, bool allocated) {
  ...
}
static void split_block(block_t *block, size_t asize) {
  size_t block_size = get_size(block);

  if ((block_size - asize) >= min_block_size) {
    write_header(block, asize, true);
    write_footer(block, asize, true);
    block_t *block_next = find_next(block);
    // below code should assert (block_size - asize > 0)
    write_header(block_next, block_size - asize, false);
    write_footer(block_next, block_size - asize, false);
    ...
  }
  ...
}

// free will `coalesce_right` two free blocks together

// @brief from header of a block to its footer
const size_t dsize = 2*sizeof(word_t);
static word_t *header_to_footer(block_t *block) {
  size_t asize = get_size(block);
  return (word_t *) (block->payload + asize - dsize);
}

static word_t *find_prev_footer(block_t *block) {
  return &(block->header) - 1;
}

void *mm_malloc(size_t size) {
  // block should be multiple of 16 bytes
  // payload is at the start of the alignment
  // round_up(n, m) = m * ((n + m - 1) / m)
  size_t asize = round_up(size + dsize, dsize);
  block_t *block = find_fit(asize);

  if (block == NULL) return NULL;

  size_t block_size = get_size(block);
  write_header(block, block_size, true);
  write_footer(block, block_size, true);

  split_block(block, asize);

  return header_to_payload(block);
}

void mm_free(void *bp) {
  block_t *block = payload_to_header(bp);
  size_t size = get_size(block);

  write_header(block, size, false);
  write_footer(block, size, false);

  coalesce_block(block);
}

Additional Information

D. Knuth, The Art of Computer Programming, vol 1, 3rd edition, Addison Wesley, 1997 (The classic reference on dynamic storage allocation)

Wilson et al, “Dynamic Storage Allocation: A Survey and Critical Review”, Proc. 1995 Int’l Workshop on Memory Management, Kinross, Scotland, Sept, 1995. (Comprehensive survey on csapp.cs.cmu.edu)

Table of Content