-------------------------------------------------- Outline: * What is garbage collection? Re-using memory. * Memory layout (CS 314 flavored). * Methods for GC and their complexity: - Reference count - Mark-sweep - Stop and copy -------------------------------------------------- We've never worried about how much memory we had: * Allocated whenever we wanted it. * Didn't worry about freeing it. This is a *tremendous* advantage of Scheme over C or Pascal. * Most programming errors are in memory allocation/freeing, and phenomenally hard to debug. * The direction today is towards these languages (most modern languages, including the J-word). But the computer has only limited memory, so you can't just allocate more whenever you need some: * Like any other material resource, you *have* to recycle it! What Scheme primitive allocates storage? CONS, of course! Anything else? Making closures, instantiating objects, applying closures (why?). Talk only about cons for now. Let's look at how cons could be implemented: Memory is more-or-less a vector: a sequence of M locations. Each location can store certain information. Its important that a location at least be large enough to store an *address* (that is, a number big enough to index another location). Our memory locations will also contain some TAG INFORMATION, indicating the TYPE of data stored in a location: - Right now, a P meaning `pointer', or - N meaning `number' We will start addresses at 1, and use P0 --- 0 isn't an address -- for the null pointer. We'll need two memory locations per cons cell, since a cons cell has two elements. For no good reason, lets make locations i and i+M/2 into one cons cell. * 1 and 11 are a cell, with head stored in 1, tail in 11. 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |P 3 |N 3 |N 5 |N 5 |P 6 |N 6 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 6 |P 1 |P 0 |P 0 |N 9 |P 0 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 What's at location 2? This list structure: (3 (5) 6) [draw box and pointer too] NOTE: this is a way of implementing cons cells in terms of a vector, could have only type in Scheme and then have cons cells implemented in terms of them (with a garbage collector). Using this implementation of cons cells: (cons A B) does this: At the next FREE location, 1 <= i <= M/2: 1. Store A at i 2. Store B at i+M/2 3. updates FREE to point to the "next free location" 4. Return Pi, a pointer to the new cons cell. NOTE: * (eq? A B) checks to see that A and B are the same (both tag and value). * (pair? A) checks to see whether the tag is P or something else. To find a free cell we can either store some special value in it that marks it as free, have a "bitmap" specifying what's free, or have all free cells in a linked list (either in the array itself or in some external list). The problem, of course, is, what happens when you run out of FREE? -------------------------------------------------- Not all the stuff in memory is actually useful. Suppose we execute the code: (define squares (map square (list 1 2 3))) what happens is, 1. Build (1 2 3) 2. Build (1 4 9) There aren't any pointers to that (1 2 3) anywhere. >>> Draw a blob for memory, two lists in it, a >>> pointer from squares to (1 4 9). It cannot be accessed. It is *garbage*, and we don't need it any longer. Suppose, in that memory above, P2 is the only pointer from memory to anywhere. The cons cells at 4 and 5 (and 7-10) can't be accessed, so they ought to be free. We determine this by tracing pointers from the *Root Set*, which has two parts: 1. The things named in the global environment can all be accessed. 2. We might run out of space in the middle of a bunch of function calls, and things accessible from them aren't garbage (the runtime stack). Nothing else could *ever* be reached by the user of the system * All the user can do is follow pointers! So, we somehow need to make everything else free. It's like pulling a net out of the mud Important fact: there is always (practically speaking) *much* more mud than net! -------------------------------------------------- There are three major techniques for doing garbage collection: 1. Reference Counts [not used much, O(M) time, "wastes" 1/3 memory when implementing cons cells] 2. Mark Sweep [takes O(M) time, fairly space efficient] 3. Stop+Copy / Baker's Algorithm / Real-time copy [takes O(net) time, "wastes" half the memory] Just the basic idea of reference count: Keep a counter on each cons cell, telling how many other locations point to it: 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |P 3 |N 3 |N 5 |N 5 |P 6 |N 6 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 6 |P 1 |P 0 |P 0 |N 9 |P 0 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |N 1 |N 1 |N 1 |N 1 |N 0 |N 1 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ When that count is 0, the location is garbage. NOTE: Location 4 is garbage, but has a pointer to it and so has non-0 ref count. Locations with reference count 0 are garbage; you can find one by walking along memory. There are two problems with this scheme 1. It's expensive. Every allocation / freeing requires an addition/subtraction, and the whole thing requires an extra 50% memory. 2. Circular structures have nonzero counts! Even when garbage. You can make circular lists with list mutation, e.g. (define a (cons 1 2)) (set! (tail a) a) [Draw box+pointer diagram] So, ref counts have rather limited applications. Examples: SmallTalk Unix FS COM/DCOM - - - - - - - - - - - - - - - - - - - - - - - - - Mark-and-sweep: find all the good stuff, and recycle everything else. To avoid infinite loops on circular structures, we mark stuff that we've already seen. * Each cons cell needs space for a MARK: (one bit) (Note -- they're probably not stored with the cells, they're probably somewhere else.) * usually represent free memory as a linked list of free cells, can use blocks of free memory (but the latter is considerably more expensive because requires compressing memory). 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |P 3 |N 3 |N 5 |N 5 |P 6 |N 6 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 6 |P 1 |P 0 |P 0 |N 9 |P 0 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 1. When memory is full, stop and CLEAR all the mark bits (leftovers from last run of mark-sweep) << MARK >> 2. Start at root and follow the accessible structure, * Mark every cell you visit * Stop when you see a marked cell, so you don't go into a cycle <> 3. Start at the end of memory, and build a new freelist. If a cell's unmarked, then it's garbage, so hook it into the freelist. 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |P 3 |N 3 |N 5 |N 5 |P 6 |N 6 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 6 |P 1 |P 0 |P 0 |N 9 |P 0 | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ | M | M | M | | | M | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 The free-list here will look like: P4 --> P5 --> P7 --> P8 --> P9 --> P10. Notes: 1. This is a good deal more compact than copying. (It does use O(M) space for the mark bits, and in the easy implementation, space for the stack) 2. It also looks through *all* of memory, so it's fairly slow. 3. There is a free implementation of a general mark-and-sweep garbage collector for C that replaces malloc. http://reality.sgi.com/boehm_mti/gc.html This is the collector used by our MzScheme implementation. -------------------------------------------------- Stop+Copy Key idea: only look at the stuff that's used. Split memory into two halves. (So, with our cons-cell hack, this means quarters.) 1. Working (aka `old') space. * This is where stuff gets allocated. 2. Copy (aka `new') space. The two halves get swapped after a garbage collection. * That is, the half that used to be called oldspace is now newspace, and vice-versa. 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 Basic idea: 1. Keep a FREE pointer, to somewhere in oldspace. * Everything from that to top of oldspace is free. * When you allocate, just increment the pointer. 2. When you hit the end of oldspace, stop the user and do a GC. 3. COPY all the accessible cells, by following heads and tails from the root set. Note that this *only* take time proportional to the space that is actually being used! 4. Swap roles of old and copy spaces, and continue the user program. -------------------------------------------------- The copying operation uses two pointers: FREE and SCAN. Let's assume that there's only one root cell. We need a special kind of pointer called a FORWARDING POINTER F n is a pointer from old-space to new-space, telling that a cell has been moved from one space to the other. Why do we need this? [Think about it!] INITIALIZE: 1. Copy the root cell from working to copy space. Set the root pointer to this location. 2. Set FREE to one cell past the copied root. 3. Set SCAN to the copied root. 4. Mark the old pair as forwarded, by setting its head to a F pointer LOOP: 5. Trace head and tail pointers of the single cell pointed to by SCAN. If they're in oldspace, then 5.1. Copy them to newspace and increment FREE 5.2. Mark each copied cell as "forwarded" with a F pointer to its copy. 6. Change head and tail pointers of current cell (under SCAN) to point to the cells in the newspace -- they always point to the old space (if they're pointers), so follow the forwarding pointers to newspace. 7. Increment SCAN. 8. If SCAN < FREE then goto 5, otherwise we're done. -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |N 6 |N 3 |P 4 |P 4 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ | | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 Root = P 2 -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |N 3 |P 4 |P 4 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 3 | | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 SCAN FREE -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |F22 |P 4 |P 4 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 |N 3 | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P22 |P 5 | | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 SCAN FREE -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |F22 |P 4 |F23 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 |N 3 |P 4 | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P22 |P23 |P 8 | | | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 SCAN FREE -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |F22 |F24 |F23 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 |N 3 |P24 |P 4 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P22 |P23 |P25 |P 4 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 SCAN FREE -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |F22 |F24 |F23 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 |N 3 |P24 |P24 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P22 |P23 |P25 |P24 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 SCAN FREE -------------------------------------------------- 1 2 3 4 5 6 7 8 9 10 +----+----+----+----+----+----+----+----+----+----+ |N 1 |F21 |F22 |F24 |F23 |P 0 |N 3 |P 0 |N 2 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P 0 |P 3 |P 5 |P 4 |P 8 |P 0 |P 7 |P 0 |P 3 |P 7 | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 11 12 13 14 15 16 17 18 19 20 =================================================== 21 22 23 24 25 26 27 28 29 30 +----+----+----+----+----+----+----+----+----+----+ |N 6 |N 3 |P24 |P24 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ |P22 |P23 |P25 |P24 |P 0 | | | | | | | | | | | | | | | | | +----+----+----+----+----+----+----+----+----+----+ 31 32 33 34 35 36 37 38 39 40 FREE SCAN And now it's done. This sounds fairly expensive: * Half of memory is idle except using GC But it goes just about as fast as possible * It only examines *valid* locations. * Most memory is garbage, and never even gets looked at Pay a lot of space, get a lot of time. * Common CS tradeoff... Cannot implement a general C collector for this. * Pointers change, and in C, some pointers might not actually be pointers! Comparing the Mark+Sweep collector with the Stop+Copy one like we just did is probably too simplistic. There are many other factors playing like virtual memory, memory caches, disk swaps etc etc etc. In additions, many variations can be implemented, for example - avoid the sweep phase of Mark+Sweep - and when allocation is needed just look for a marked cell. -------------------------------------------------- Ephemeral garbage collectors are based on the concept of the LIFETIME of an object: * Usually, when you do a computation, you make - a bunch of garbage - a few objects that hang around a long time. Once something has been around a medium while, it will probably stay a long while, so there's no point in repeatedly trying to GC it. So, a smartish garbage collector will concentrate on a small space where recent activity has been, and only infrequently go over the whole memory. This is done by using several memory segments - each one is called a generation: * GC happens in every generation independently. * When an object survives N GCs in one generation area it is moved to the next one. This way we get more informtion about the objects that we can use for further optimization: * the younger generations can be GC'd with a Stop+Copy: we expect a lot of junk lying around and small pieces to actually move. * and the older generations with Mark+Sweep: we usually don't collect much garbage so most cells are scanned anyway, plus we will probably find large objects here that we don't want to move around.