The Reclaim Notification Patch
These are rather technical notes about a patch to the
Boehm-Demers-Weiser Conservative Garbage Collector which adds a feature
which in lack of a better name I call “reclaim notification”. This is a
low-level feature which is aimed to support efficient hash-consing and
finalisation.
The Issues
First off, it should be noted that the performance finalisation is
usually a non-issue if recommendations for their use is followed. The main
use of finalisation is to free up non-critical resources. (Critical
resources should be explicitely managed, since finalisation is not
guaranteed.) Since such resources are typically few compared to the
abundance of system memory, the overhead of finalisation is small. So why
bother?
My interest in this stems from a more exotic use of the collector,
hash-consing. This mechanism can be implemented in terms of finalisers or
int terms of weak-pointers, but since neither is a good fit, it's useful to
think of hash-consing as an independent mechanism.
Hash-consing is a
technique that allows sharing of objects constructed independently but which
happen to be identical. In addition to reducing memory overhead in
applications where such sharing is likely, it also means that equality over
complex expression trees reduces to a simple pointer comparison. The
technique can be used for expression trees, as exemplified by the ATerm tree-handling
library which implements its own garbage collector, and my own cuex
library of culibs which uses the Boehm-Demers-Weiser
collector.
The current libgc implementation handles weak pointers and schedules
finalisation at the end of a collection, when the mark-bits are at a
consistent state. The actual finalisation happens later, but if the number
of finalisers are comparable to the number of heap objects, there is still a
significant overhead at this critical phase when threads cannot use the
collector. There is also a significant time and memory overhead when
finalisation is used extensively for small objects. In heavy
tree-processing application, the main part of the memory may be such small
objects.
A Solution
The two things we want to accomplish is
- Implement a mechanism to support finalisation and hash-consing entirely
outside the critical point where the collector marks are consistent.
- Reduce time and memory overhead.
Roughly one phase of garbage collection is
- Follow the root set and recursively mark all accessible objects.
Recursivity is handled by pushing objects on a stack, and iteratively
marking objects on the stack and pushing links.
- Do a final mark from dirty pages, with world stopped.
- When there is nothing more to mark, lock the collector. Marks are now
consistent.
- Walk through finalisers, schedule them and mark accessible objects.
Also, make disappearing links disappear.
- Unlock the collector.
- Process finalisation in parallel (preferred), or trigger them from
allocation functions. After each finaliser is run, remove it so that the
finalised object can be collected on the next phase.
The proposed solution is to add a light-weight low-level feature upon
which finalisation and hash-consing can be implemented:
- Add the following properties for objects kinds:
- A flag which indicates that all objects of this kind shall be
followed during the mark phase, even if they are not themself marked,
essentially treating them as a root set. This may be needed for
finalisation, but not for hash-consing.
- Add a callback, the reclaim notifier, that is guaranteed to be
called for objects of this kind after the word has be started and
before the object is reclaimed. The callback is allowed to resurrect
the object, as indicated by its return code. This latter added to
avoid having to check the mark-bit of objects upon hash-consing, since
this operation can be slow when not done is bulk.
- The mark phase is extended to support the follow-unconditionally
flag.
- I suggest one of the following two methods for running reclaim
notifiers
- Simple. Call the notifiers when a heap block is reclaimed.
This has the disadvantage that the notifiers are called from threads
allocating memory, potentially introducing concurrency issues for
finalisers. With a bit of care, this can be gracefully handled for
hash-consing. The general rule is: Don't allocate memory while
holding a lock that the finaliser may hold.
- Proper. Schedule blocks of kinds with reclaim notifiers
for traversal in a dedicated finalisation thread. Each block is
marked so that their objects can not be reclaimed before the finaliser
thread is done with it. Some care must be taken so that the finaliser
thread does not lag behind the other threads. Finalisers can now
safely allocate memory and share locks with allocating threads.
What we gain from this:
- There is no per-finaliser or per-hash-cons work to do in the
stopped-world state.
- Objects with finalisers can be reclaimed in the same collection that
they become reclaimable. The hash-consing implemenation in culibs still
needs an extra collection.
- The memory overhead per finaliser is only what is needed to store the
finaliser. Normally single pointer to either a function or a closure
struct should suffice.
- Languages which use vtables or similar per-class finalisers can write
a reclaim notifier which calls the appropriate vtable entry. Thus, there
is no additional per-object overhead for finalisation.
- Hash-consing doesn't require per-object finalisers, either. Thus, the
memory overhead is only that of the hash-table, which includes a
chain-link within the object itself.
Limitations:
- Objects with finalisers must be of suitable GC-kind and must include
information about the finaliser for the corresponding reclaim notifier.
Thus, it's not possible to register these finalisers on object allocated
with the default kinds.
I suspect the common case is to register finalisers in connection with
allocation.
- I'm not sure this is feasible for disappearing links. That could be
investigated, but I'd suggest using the existing scheme.
- One inconvenient technicality is that the reclaim notifier must be
able to distinguish an object which is part of a free-link from a live
object.
My suggestion is to store an odd number in the first word of the object.
This is where the collector stores the free-link next-pointer, so it will
always be even for a free-linked object.
In case of finalisers, this word can be used to store the address of the
finaliser closure, adding one byte to its address.
Alternatively, the fact that the rest of free-linked objects is cleared
can be utilised.
The high-level finalisation interface in the patch hides this detail from
the end-user.
Better solutions for handling this are welcome.
The Patch
In the following is a summary of what the patch does. To make sence of
it, you should have the sources to the collector, and the patch itself,
which is found in the download directory.
Update: The patch is now named
gc-GC_VERSION-disclaim-PATCH_VERSION.patch.
Extension to Object Kinds and Heap Block Headers
Reclaim notification can be enabled for user-defined object-kinds. Most
importantly, the callback is registered along with closure data. In
addition there is a flag to follow pointers from unmarked objects in
associated heap blocks during the mark phase. The latter is optional and
prevents collection of objects reachable from reclaim notifiers.
The patch also includes a default kind for finalisable objects.
A debug-enabled version should be added if this patch is accepted by the GC
developers.
The Essential Algorithm Changes
This part of the patch makes sure reclaim notifiers are called before
objects of the associated kinds are reclaimed. The notifiers are allowed to
resurrect objects.
I found it necessary to support resurrection to support hash-consing,
because of the late callback to the notifier.
Since the notifiers are not called while the world is stopped, they can not
remove unmarked objects from the hash-consing hash-table in time.
That is, during the time between the world is stopped and the time objects
are reclaimed, the application may receive potentially recycleable objects.
The notifier detects this case and prevents these object from being
reclaimed.
Support for Finalization
Build Infrastructure and Test Case
In addition to the above there are two test cases and miscellaneous
changes to the build files.
Last updated 2007-08-21 by
Petter Urkedal.