-----
 
FAQ
Benchmarks
Credits
Documentation
Download
Hemlock
Home
Install
News
Platforms
Ports
Projects
Search
Support
 Documentation: Understanding the garbage collector

The garbage collector (or GC) is the component of the CMUCL runtime that is responsible for recycling dynamically allocated memory. The GC traverses the lisp heap, starting from the roots (global variables, contents of the lisp stack, contents of the register file) and follows references to identify objects that are reachable. The memory occupied by objects that are no longer reachable (called garbage) is made available for reuse.

When CMUCL is started, the runtime reserves a number of contiguous regions of address space for the lisp heap. The lisp heap is divided into dynamic, static and read-only spaces. The size of these spaces is fixed on startup (which is why CMUCL looks like it uses a huge amount of memory right after being started -- in fact it is just reserving address space, and will only use the memory if necessary). The size of the dynamic space can be set by the -dynamic-space-size commandline option.

Newly created objects (both code and data) are allocated in the dynamic space. They may be moved to static or read-only space by the function purify (that is run when you save a new lisp heap). The garbage collector only scans dynamic space.

Precise and conservative collectors

On platforms other than x86, CMUCL uses a precise collector. This means that when tracing references through memory to determine which objects are not reachable from the roots, it is able to determine exactly which objects are not reachable (and thus available for reuse).

In contrast, CMUCL uses a conservative collector on x86 platforms. This means that when tracing references through memory the GC is not always able to determine whether a given memory location contains a reference (which it should follow for reachability analysis), or just data (which it shouldn't follow). In these situations where the GC cannot decide, it is cautious (or conservative) and assumes that the location contains a reference. This means that in some cases the conservative GC is unable to reclaim storage that is actually garbage, which leads to wasted memory. However, this leakage is often only temporary, since the register or the stack location that was mis-interpreted will likely be overwritten by the execution of the program. Experience shows that this "leakage" is rarely a significant problem in most applications.

Why use a conservative collector given its disadvantages over a precise collector? On platforms that use a precise garbage collector, CMUCL partitions the register file into descriptor registers (which contain references to lisp objects) and non-descriptor registers (containing machine integers, untagged fixnums, characters, etc). This means that when a garbage collection occurs, it knows exactly which registers should be included as roots of the reachability analysis. Furthermore, the lisp stack (containing call frames) is separate from the C stack (containing foreign frames, signal handler contexts, etc), so the scan of the stack for roots can be done exactly. The x86 platform has such a pitiful number of available registers that it would be unreasonable to partition the register file in this way; this is why a conservative collector is used. Furthermore, on x86 platforms the lisp stack is shared with the C stack, so the stack must also be scavenged conservatively.

Technical detail: descriptor and non-descriptor registers

On non-x86 platforms the garbage collector classifies values into three categories:

  • pointer descriptor objects such as FunctionPointer, ListPointer, InstancePointer, OtherPointer, that have low tags in (#b001 #b011 #b101 #b111). These objects can be kept in a descriptor register, because they are fully tagged. In fact, they must be kept in a descriptor register, because the collector needs to see and modify them (when the collector moves an object, it must update any pointers to that object).
  • immediate descriptor objects, such as odd and even fixnums, OtherImmediate. These objects can be kept in descriptor registers, because the collector won't think that they are pointer references. They can also be kept in non-descriptor registers, because nothing bad will happen if the collector sees them (the collector can distinguish them from lisp-pointers by looking at their tag).
  • non-descriptor objects such as machine-word-sized integers, untagged characters, unboxed system-area-pointers, unboxed single-floats and double-floats. These objects can't be kept in a descriptor register, because doing so might confuse the collector.

See the files src/compiler/generic/primtype.lisp and src/compiler/sparc/vm.lisp in the CMUCL source code for more details.

The generational collector

On non-x86 platforms, CMUCL uses a two-space stop-and-copy collector based on the Cheney algorithm. The collector is triggered after a certain amount of allocation, and interrupts the lisp application until the garbage collection has terminated. During a collection, the garbage collector examines the lisp heap, walking objects from the roots. It copies reachable objects from the current memory space to the newspace, making the necessary pointer corrections so that references to an object remain valid once is has been copied to its new location.

On x86 platforms CMUCL uses a generational mostly-copying collector, which is signalled by the presence of :gencgc on the *features* list. A generational (or generation-scavenging) collector partitions the lisp heap according to the age of objects, and focuses its attention on younger objects. New objects are allocated in the youngest generation (often called the nursery), and are promoted to an older generation each time they survive a garbage collection. In an application where most allocated objects are short-lived (which is the case in many applications), a generational collector is efficient because most garbage collections only examine a portion of the lisp heap (it saves time by ignoring older objects), and because the spatial locality of objects in the nursery can improve cache use.

The garbage collector performs a collection when generation 0 is full. It starts by examining the objects in generation 0. Reachable objects are promoted to generation 1. If the collection of generation 0 did not release sufficient memory, generation 1 is collected, and so on until generation number 6.

The CMUCL generational collector is "mostly-copying". An object that has an ambiguous reference (where the conservative nature of the collector means that it's unsure whether a memory location contains a reference) is said to be pinned in place; they are promoted in-place to the oldspace generation. The granularity of age annotation is a page, and a page stays in place (isn't copied) if it contains a pinned object.

The collector treats large objects (such as large arrays) specially, for efficiency. [FIXME expand] The generational collector is able to use the page protection mechanisms of the MMU to avoid scavenging pages that don't contain pointers to younger generations.

Triggering a garbage collection

Execution of the garbage collector will automatically be triggered once a certain number of megabytes have been allocated. Before trying to analyze the memory usage of your application, it may be useful explicitly to trigger a garbage collection. This can be done by calling the CMUCL function ext:gc.

The generational collector normally carries out only partial collections (it scans a subset of all the generations). This may lead to long-lived objects that become garbage taking a long time to be reclaimed. In order to force a full garbage collection, use

   (gc :full t)

The :full keyword argument is only available when the generational collector is present.

Analyzing memory usage

The standard Common Lisp function room provides information on the status of memory allocation. It provides information such as:

   Dynamic Space Usage:     5,851,432 bytes.
   Read-Only Space Usage:  18,408,744 bytes.
   Static Space Usage:      2,311,240 bytes.
   Control Stack Usage:           452 bytes.
   Binding Stack Usage:            96 bytes.
   The current dynamic space is 0.
   Garbage collection is currently enabled.

With a second argument of T, it prints additional information, including the number of objects of each type that are present in the lisp heap. This can be useful to know what class of data structures are filling up the heap. If you suspect that the garbage collector is forgetting to reclaim certain objects, you may be interested in reading the page analyzing memory usage.

Note that the room function only displays information on the lisp heap; it does not tell you about allocations in foreign space (objects allocated via the foreign function interface, for example using malloc() from C). You will need to use tools from your operating system (such as analyzing the contents of the file /proc/<pid>/maps on Linux).

Tuning the garbage collector

While the CMUCL garbage collector functions reasonably well in general, for certain applications it may be useful to fiddle with various parameters.

  • The variable ext:*bytes-consed-between-gcs* determines the number of bytes of allocation that will trigger a garbage collection. A lower number will trigger the garbage collector more frequently, but each collection will take less time; this is good for interactive applications where response time is important. A higher value will cause fewer garbage collections, and should decrease the overall time spent in GC. A useful idiom is to increase this value around code where you will be allocating large amounts of memory, as follows:
       (let ((ext:*bytes-consed-between-gcs*
               (* ext:*bytes-consed-between-gcs* 5)))
          ;; ... lots of allocation ...
          )
    
  • The macro sys:without-gcing allows you to execute forms with the garbage collector disabled.
  • Use the function ext:save-lisp with the purify option enabled, in order to move code and data to static space. This will improve your application's GC characteristics, because the static and read-only spaces are not scanned by the garbage collector.
  • The variable ext:*gc-verbose* can be used to disable the status messages that are printed by the garbage collector. This may give the illusion of reducing GC overhead.
    ; [GC threshold exceeded with 20,168 bytes in use.  Commencing GC.]
    ; [GC completed with 22,128 bytes retained and -1,960 bytes freed.]
    ; [GC will next occur when at least 25,022,128 bytes are in use.]
    
  • The alien variable gencgc_verbose can be set to 1 or 2 in order to print extra information concerning the functioning of the generational collector. This prints information like the following, showing the status of each generation.
    USER> (alien:def-alien-variable ("gencgc_verbose" gencgc-verbose) c-call::int)
    #<alien::heap-alien-info (system:foreign-symbol-address "gencgc_verbose" :flavor :data) (alien:signed 32)>
    USER> (setf gencgc-verbose 2)
    2
    USER> (gc :full t)
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:  1471     0     0     0  6022632  2584 10000000    0   0  0.0000
              1:   254    70 97657     0 401296280 33896 411252792 97843   1  2.9998
              2:     0     0     0     0        0     0  2000000    0   0  0.0000
              3:     0     0     0     0        0     0  2000000    0   0  0.0000
              4:     0     0     0     0        0     0  2000000    0   0  0.0000
              5:     0     0     0     0        0     0  2000000    0   0  0.0000
       Total bytes alloc=407318912
    Starting GC of generation 0 with raise=1 alloc=6022632 trig=10000000 GCs=0
    *W control stack vector length 0
    Non-movable pages due to conservative pointers = 1, 4096 bytes
    Scavenge static space: 2364848 bytes
    GC of generation 0 finished:
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:     0     0     0     0        0     0 10000000    0   0  0.0000
              1:   261    71 97657     0 401324584 38360 411252792 97843   1  3.9995
              2:     0     0     0     0        0     0  2000000    0   0  0.0000
              3:     0     0     0     0        0     0  2000000    0   0  0.0000
              4:     0     0     0     0        0     0  2000000    0   0  0.0000
              5:     0     0     0     0        0     0  2000000    0   0  0.0000
       Total bytes alloc=401324584
    Starting GC of generation 1 with raise=1 alloc=401324584 trig=411252792 GCs=1
    *W control stack vector length 0
    Non-movable pages due to conservative pointers = 5, 20480 bytes
    Scavenge static space: 2364848 bytes
    ** copy_large_object: 400000008
    GC of generation 1 finished:
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:     0     0     0     0        0     0 10000000    0   0  0.0000
              1:     0     0     0     0        0     0 10000000    0   0  0.0000
              2:   228    71 97657     0 401214056 13720  2000000    0   0  0.0000
              3:     0     0     0     0        0     0  2000000    0   0  0.0000
              4:     0     0     0     0        0     0  2000000    0   0  0.0000
              5:     0     0     0     0        0     0  2000000    0   0  0.0000
       Total bytes alloc=401214056
    Starting GC of generation 2 with raise=1 alloc=401214056 trig=2000000 GCs=0
    *W control stack vector length 0
    Non-movable pages due to conservative pointers = 5, 20480 bytes
    Scavenge static space: 2364848 bytes
    ** copy_large_object: 400000008
    GC of generation 2 finished:
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:     0     0     0     0        0     0 10000000    0   0  0.0000
              1:     0     0     0     0        0     0 10000000    0   0  0.0000
              2:     0     0     0     0        0     0 10000000    0   0  0.0000
              3:   228    71 97657     0 401214056 13720  2000000    0   0  0.0000
              4:     0     0     0     0        0     0  2000000    0   0  0.0000
              5:     0     0     0     0        0     0  2000000    0   0  0.0000
       Total bytes alloc=401214056
    Starting GC of generation 3 with raise=1 alloc=401214056 trig=2000000 GCs=0
    *W control stack vector length 0
    Non-movable pages due to conservative pointers = 5, 20480 bytes
    Scavenge static space: 2364848 bytes
    ** copy_large_object: 400000008
    GC of generation 3 finished:
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:     0     0     0     0        0     0 10000000    0   0  0.0000
              1:     0     0     0     0        0     0 10000000    0   0  0.0000
              2:     0     0     0     0        0     0 10000000    0   0  0.0000
              3:     0     0     0     0        0     0 10000000    0   0  0.0000
              4:   228    71 97657     0 401214056 13720  2000000    0   0  0.0000
              5:     0     0     0     0        0     0  2000000    0   0  0.0000
       Total bytes alloc=401214056
    Starting GC of generation 4 with raise=1 alloc=401214056 trig=2000000 GCs=0
    *W control stack vector length 0
    Non-movable pages due to conservative pointers = 5, 20480 bytes
    Scavenge static space: 2364848 bytes
    ** copy_large_object: 400000008
    GC of generation 4 finished:
       Generation Boxed Unboxed LB   LUB    Alloc  Waste   Trig    WP  GCs Mem-age
              0:     0     0     0     0        0     0 10000000    0   0  0.0000
              1:     0     0     0     0        0     0 10000000    0   0  0.0000
              2:     0     0     0     0        0     0 10000000    0   0  0.0000
              3:     0     0     0     0        0     0 10000000    0   0  0.0000
              4:     0     0     0     0        0     0 10000000    0   0  0.0000
              5:   228    71 97657     0 401214056 13720  2000000    0   0  0.0000
       Total bytes alloc=401214056
    
  • The function sys:scrub-control-stack can be used to zero the unused portion of the control stack. This avoids old objects being kept alive due to a reference from an uninitialized variable on the control stack.
  • (cl::gencgc-print-generation-stats 1) [FIXME expand]
  • The foreign variable gencgc_oldest_gen_to_gc determines the oldeest generation that will be subject to garbage collection by default (in partial collections). The default value enables GC on all generations. Setting this variable to 0 effectively disables the generational nature of the collector. In some applications, generational GC may not be useful, because there are no long-lived objects. An intermediate value (between 0 and 6) may be appropriate after moving long-lived data into an older generation, in order to avoid an unnecessary GC of this long-lived data.

Further information

by Eric Marsden, with contributions from Pierre Mai and Daniel Barlow.

Printable version of this page
CMUCLon

Last modified 2015-10-17 by <webmaster@cmucl.cons.org>
Copyright © 1999-2010 CMUCL Project
Validate links, HTML, stylesheet.