Memory Management: Free Space and Dynamic Allocation
Total Page:16
File Type:pdf, Size:1020Kb
Memory Management: Free space and dynamic allocation M1 MOSIG – Operating System Design Renaud Lachaize Acknowledgments • Many ideas and slides in these lectures were inspired by or even borrowed from the work of others: – Arnaud Legrand, Noël De Palma, Sacha Krakowiak – David Mazières (Stanford) • (most slides/figures directly adapted from those of the CS140 class) – Randall Bryant, David O’Hallaron, Gregory Kesden, Markus Püschel (Carnegie Mellon University) • Textbook: Computer Systems: A Programmer’s Perspective (2nd Edition) • CS 15-213/18-243 classes (some slides/figures directly adapted from these classes) – Remzi and Andrea Arpaci-Dusseau (U. Wisconsin) 2 – Textbooks (Silberschatz et al., Tanenbaum) Outline • Introduction – Motivation – Fragmentation • How to implement a memory allocator? – Key design decisions – A comparative study of several simple approaches – Known patterns of real programs – Some other designs • Implicit memory management (garbage collection) 3 Dynamic memory allocation – Introduction (1) • Almost every program uses it – Gives very important functionality benefits • Avoids statically specifying complex data structures • Avoids static overprovisioning of memory • Allows having data grow as a function of input size – But can have a huge impact on performance • A general principle, used at several levels of the software stack: – In the operating system kernel, to manage physical memory – In the C library, to manage the heap, a specific zone within a process’ virtual memory – (And also possibly) within an application, to manage a big chunk of virtual memory provided by the OS 4 Dynamic memory allocation – Introduction (2) • Today’s focus: how to implement it – Lectures draws on [Wilson et al.] (good survey from 1995) • Some interesting facts (on performance) – Changing a few lines of code can have huge, non-obvious impact on how well an allocator works (examples to come) – Proven: impossible to construct an “always good” allocator – Surprising result: after decades, memory management is still poorly understood 5 Why is it hard? • Must satisfy arbitrary sequence of alloc/free operations • Easy without free: – Set a pointer to the beginning of some big chunk of memory (“heap”) and increment on each allocation • Problem: free creates holes (“fragmentation”). Result: lots of free space but cannot satisfy request • Why can’t we just move everything to the left when needed? – This requires to update memory references (and thus to know about the semantics of data) 6 External Fragmentation 243 class) - • Occurs when there is enough aggregate heap 213/18 - 15 memory, but no single free block is large enough – p1 = malloc(4) p2 = malloc(5) p3 = malloc(6) free(p2) p4 = malloc(6) Oops! (what would happen now?) (adapted from the following source: Carnegie Mellon University University Mellon Carnegie source: following the from (adapted 7 Internal Fragmentation 243 class) - • For a given block, internal fragmentation occurs if 213/18 - payload is smaller than block size 15 – block Internal Internal payload fragmentation fragmentation • Caused by: – overhead of maintaining heap data structures • (e.g., memory footprint of metadata headers/footers) – padding for alignment purposes – explicit policy decisions • (e.g., decision to return a big block to satisfy a small request, (adapted from the following source: Carnegie Mellon University University Mellon Carnegie source: following the from (adapted in order to make the operation go faster) 8 More abstractly • What an allocator must do: – Track which parts of memory are in use, and which parts are free – Ideally: no wasted space, no time overhead • What the allocator cannot do: – Control order, number and size of the requested blocks – Change user pointers (as a consequence, bad placement decisions are permanent) • The core fight: minimize fragmentation – Application frees blocks in any order, creating holes in “heap” – If holes are too small, future requests cannot be satisfied 9 What is (external) fragmentation? • Inability to use memory that is free • Two factors required for (external) fragmentation – Different lifetimes: • If adjacent objects die at different times, then fragmentation • If they die at the same time, then no fragmentation – Different sizes: • If all requests have the same size, then no fragmentation • (As we will see later, in the context of virtual memory, paging relies on this to remove external fragmentation) 10 Outline • Introduction – Motivation – Fragmentation • How to implement a memory allocator? – Key design decisions – A comparative study of several simple approaches – Known patterns of real programs – Some other designs • Implicit memory management (garbage collection) 11 Important design decisions (1/5) • Free block organization: How do we keep track of free blocks? • Placement: How do we choose an appropriate free block in which to place a newly allocated block? • Splitting: After we place a newly allocated block in some free block, what do we do with the remainder of the free block? • Coalescing: What do we do with a block that has just been freed? 13 Important design decisions (2/5) • Free block organization: How do we keep track of free blocks? – Common approach: “free list” i.e., linked list of descriptors of free blocks – Multiple strategies to sort the free list – For space efficiency, the free list is stored within the free space! – (There are also other approaches/data structures beyond free lists, e.g., balanced trees) 14 Important design decisions (3/5) • Placement: How do we choose an appropriate free block in which to place a newly allocated block? – Placement strategies have a major impact on external fragmentation – We will study several examples soon • (best fit, first fit, …) – Ideal: put block where it will not cause fragmentation later • Impossible to guarantee in general: requires future knowledge 15 Important design decisions (4/5) • Splitting: After we place a newly allocated block in some free block, what do we do with the remainder of the block? Two choices: – Keep the remainder within the chosen block • Simple, fast • but introduces more internal fragmentation – Split the chosen block in two and insert the remainder block in the free list • Better with respect to internal fragmentation (less wasted space) • … But requires more work (and thus more time), which may be wasted if most remainder blocks are useless (too small) 16 Important design decisions (5/5) • Coalescing: What do we do with a block that has just been freed? – The adjacent blocks may be free – Coalescing the newly freed block with the adjacent free block(s) yields a larger free block – This helps avoiding “false external fragmentation” – Different strategies: • Immediate coalescing: systematic attempt whenever a block is freed – This may sometimes work “too well” • Deferred: only on some occasion (e.g., when we are running out of space) or periodically 17 Impossible to “solve” fragmentation • If you read research/technical papers to find the best allocator – All discussions revolve around trade-offs – Because there cannot be a best allocator • Theoretical result – For any possible allocation algorithm, there exists streams of allocation and deallocation requests that defeat the allocator and force it into severe fragmentation • How much fragmentation should we tolerate? – Let M = bytes of live data, nmin = smallest allocation size, nmax = largest allocation size – How much gross memory required? – Bad allocator: M . (nmax / nmin) • (uses maximum size for any size) – Good allocator: ~ M . log(nmax / nmin) 19 Pathological example • Example: Given allocation of 7 20-byte blocks – What is a bad stream of frees and then allocates? – Free every one block out of two, then alloc 21 bytes • Next: we will study two allocators (placement strategies) that, in practice, work pretty well: “best fit” and “first fit” 21 Outline • Introduction – Motivation – Fragmentation • How to implement a memory allocator? – Key design decisions – A comparative study of several simple approaches – Known patterns of real programs – Some other designs • Implicit memory management (garbage collection) 22 Best fit • Placement strategy: minimize fragmentation by allocating space from block that leaves smallest fragment – Data structure: heap is a list of free blocks, each has a header holding block size and pointer to next – Code: Search free list for block closest in size to the request (exact match is ideal) – During free, (usually) coalesce adjacent blocks • Problem: Sawdust – Remainder so small that, over time, we are left with “sawdust” everywhere • Implementation? (go through the whole list? maintain sorted list?) 24 First fit • Strategy: pick the first block that fits – Data structure: free list – Code: scan list, take the first one – Implementation: Multiple strategies for sorting the free list: LIFO, FIFO or by address • LIFO: put free block on front of list – Simple but causes higher fragmentation (see details on next slide) – Potentially good for cache locality • Address sort: order free blocks by address – Makes coalescing easy (just check if next block is free) – Also preserves empty/idle space (locality good when paging) • FIFO: put free block at end of list 27 First fit: Nuances • First fit sorted by address order, in practice: – Blocks at front preferentially split, ones at back only split when no larger one found before them – Result? Seems to roughly sort free list by size – So? Makes first fit operationally similar to best fit: a first fit of a sorted list