Memory management Review Outline -- explicit management (malloc/free) -- basic idea -- goals/trade-offs -- simple implementation -- malloc -- challenges/tools -- garbage collection -- reference counting -- mark and sweep -- 3-color http://www.cs.princeton.edu/courses/archive/spr08/cos217/lectures/13Memory.ppt http://www.cs.princeton.edu/courses/archive/spr08/cos217/lectures/14Memory.ppt ftp://ftp.cs.utexas.edu/pub/garbage/gcsurvey.ps Intro Basic problem: Allocating/deallocating variable-sized storage - mostly in context of process malloc/free new/gc -- many of same principles apply in other contexts --- free space on disk --- OS internals --- OS segmentation --- user-level allocator - example -- process address space ----------------- | stack | | \|/ | | | | | | | | | | | | | | | | | | | | | | | | | | /|\ | | heap | | | | globals | | | | code | | free (why?) | ------------------ - heap allows creation of on-demand data structures -- e.g., when programmer doesn't know right size or how many until run time - naive allocator <-- break ------------------ <-- top | | | | | | | heap | | | | | | | ------------------ malloc(size){ ret = top; while(top + size >= break){ sbrk(CHUNK_SIZE); break += CHUNK_SIZE); } top = top + size; // and/or sbrk() } free(void *ptr){ return; } - problems with naive allocator -- inefficient/memory leak -- alignment of allocated data structures - other requirements -- fast -- general (any workload) -- allocate all dynamic data structures on heap - better allocator add header and footer (for simplicity, I'll put lots of info in it DA: space overhead) ------------- | free/used | header | size | | next free | <-- only valid if free == 1 | prev free | <-- only valid if free == 1 ............. | | | | | [user] | body | | | | | [padding] | ............. | free/used | footer | size | ------------- -- Keep doubly linked list of free blocks sorted by address [picture] -- malloc() scan list from current pointer to find a big enough block --- if too big, split it; insert remainder to free list (first fit) [picture] --- if none, sbrk; add big new chunk to free list; then split it and add remainder to free list [picture] --- return pointer -- free(usrptr) calculate block pointer from usrptr find next free block (how? -- size pointer gets us to next header; etc.) find prev free block (how? -- footer) insert to free list if next or prev is adjacent, coalesce blocks How is this approach? -- time allocate O(n) free O(n) -- space --- issue 1: big headers and footers [[optimization: next/prev in body]] --- issue 2: first fit --> fragmentation [[optimization: best fit; but how to do best fit efficiently?]] [[QUESTION]] how to do better? -- segregated free lists -- different lists of different approx sizes [picture] --> Allocation is fast O(1)-ish [[but coalesce gets more complex and slower]] --> need to find by size and by neighbor... solution? -- two free lists -- global sorted by address size sorted by address -- tree of free blocks sorted by size -- tree of free blocks sorted by address allocate O(log(N)) free O(log(N)) space (put extra pointers in [user] --> min alloc size is 6 pointers, but otherwise pretty good...) Garbage collection avoid (too few frees) memory leaks avoid double-free (too many frees) (related to safe language v. unsafe unsafe allows pointer arithmatic, overflow end of array, etc. Not strictly speaking garbage collection v. explicit issues. But related) -- solution 1: Reference counting often used in C programs for subset of objects e.g., memory pages in kernel (core map) other uses -- files/inodes in file system ... basic idea: -- object has extra field -- reference count -- add work on pointer manipulation --- remove pointer from existing object decrement ref count; free if zero (on free: examine all pointers in object; decrement their objects' ref counts) --- add pointer to existing object increment reference count problem: cycles problem: efficiency (adds cost to pointer manipulation) Assume safe language (java, scripting) basic idea -- find all reachable data structures; others are free e.g., Tri-color [[ picture -- a bunch of objects; some with pointers to each other ]] 3 sets: white -- condemned set (candidates for gc) black -- reachable from root (must stay) grey -- reachable from root (must stay) but children not scanned initially: all objects white (except root (grey)) objects move from white->gray->black (never in other direction) while(grey set is not empty) e = select a grey element foreach pointer p in e if p is white, make it grey if p is black or grey do nothing finally put all white elements on the free list cost: O(# reachable + # free) optimizations: (1) Incremental instead of "stop and copy" (which introduces "the awkward pause") -- let program run a bit, then garbage collect a bit, then run program a bit... --> if program mutator writes to a black object's pointer, we could miss reachable object (since we won't traverse black object again) --> on update of black object pointer to point to white object either make black object gray or make destination gray (2) Copying garbage collectors (address external fragmentation; reduce cost) e.g., semispace allocate in space 0; when space 0 is full move all live objects to "space 1"; continue allocating in space 1 when space 1 full; move all live objects to space 0 etc. --> cost O(# reachable) (3) Generational reduce cost most objects live a very short time some objects live a long time --> segregate objects by age into multiple areas [picture] --> garbage collect "new" areas more frequently than "old" ones --> survivors promoted to "older" area --> cost O(# reachable in target generation) (Challenge: tracking pointers that extend across generations --> indirection table or list of outbound pointers from each generation...)