268 lines
7.9 KiB
ReStructuredText
268 lines
7.9 KiB
ReStructuredText
.. _introduction:
|
|
|
|
.. toctree
|
|
:maxdepth: 2
|
|
|
|
Introduction
|
|
============
|
|
|
|
The ``xo-alloc`` library provides a in incremental, generational collector for c++ code.
|
|
|
|
Features:
|
|
|
|
* *incremental* - can reasonably expect short pause times.
|
|
* *generational* - focuses effort on collecting young objects,
|
|
on the basis that they're more likely to be garbage.
|
|
* *compacting* - each garbage collection cycle evacuates survivors to contiguous memory,
|
|
so effect is to defragment.
|
|
* *collects cycles* - collection algorithm naturally collects cyclic references
|
|
|
|
Tradeoffs:
|
|
|
|
* Application is responsible for spilling register values and protecting hardware stack,
|
|
since garbage collector cannot indepndently distinguish collectable object pointers from
|
|
non-pointer values.
|
|
|
|
* GC will not spontaneously run without permission. Instead will set a pending bit, with GC
|
|
occurring only when application releases it (e.g. when stack+registers are known to be empty of values
|
|
subject to GC).
|
|
|
|
* GC implementation is single-threaded. It cannot run in parallel with the mutator (i.e. application code)
|
|
In return this allows GC to be only lightly coupled with application.
|
|
|
|
* GC divides each generation into separate from- and to- spaces. A collection cycle copies surviving
|
|
objects out of from-space. Once complete, the entire from-space is treated as empty, and available to
|
|
become to-space on a future cycle. This means that at any time only half of allocated memory is available
|
|
to the application; the rest is waiting to receive survivors from the next GC cycle.
|
|
|
|
Design
|
|
------
|
|
|
|
Garbage Collector
|
|
^^^^^^^^^^^^^^^^^
|
|
|
|
The garbage collector supports two generations, labelled *nursery* and *tenured*.
|
|
Nursery objects that survive two collection cycles are promoted to tenured space.
|
|
Nursery and tenured objects are kept in separate memory areas, instead of being interspersed.
|
|
|
|
Collection cycles come in two flavors:
|
|
|
|
1. *incremental* collections - these collect only the nursery space.
|
|
|
|
2. *full* collections - these collect both nursery and tenured spaces.
|
|
Full collection may incur noticeable GC pauses.
|
|
|
|
Application Interaction
|
|
^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Application code that interacts with GC has several responsibilities.
|
|
|
|
1. application must explicitly invoke GC, when convenient. Since in general any GC-eligible object
|
|
may get moved by the collector: once a collection cycle completes,
|
|
it's up to the application to re-load pointers from memory addresses
|
|
(GC roots) that have been shared with the collector.
|
|
|
|
2. application must identify a set of GC roots. GC preserves everything reachable from any GC root
|
|
|
|
3. The collector needs to know how to traverse GC-managed objects.
|
|
We teach it this by requiring that such objects inherit the ``xo::Object`` interface,
|
|
and implement auxiliary function detailed below.
|
|
|
|
4. GC also needs to know when a mutation alters a pointer from one GC-managed object to another.
|
|
In particular, GC needs to track pointers from tenured space into nursery space,
|
|
and update them when an incremental collection moves nursery objects.
|
|
We do this by requiring application code use a GC-provided assignment primitive
|
|
on GC-eligible pointers.
|
|
|
|
|
|
Example GC Use
|
|
--------------
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
#include "xo/object/List.hpp" // polymorphic List with GC support
|
|
#include "xo/object/String.hpp" // string type with GC support
|
|
#include "xo/alloc/GC.hpp"
|
|
|
|
int main() {
|
|
using xo::gc::Config;
|
|
using xo::obj::String;
|
|
using xo::obj::List;
|
|
using xo::gp;
|
|
|
|
Config config = { .initial_nursery_z_ = 50*1000,
|
|
.initial_tenured_z_ = 10*1000*1000,
|
|
.debug_flag_ = false };
|
|
|
|
up<GC> gc = GC::make(config);
|
|
|
|
Object::mm = gc; // use GC for allocation of Object (+ derived classes)
|
|
|
|
gc->disable_gc(); // gc forbidden
|
|
|
|
// tiny example data structure
|
|
gp<String> s1 = String::copy("hello");
|
|
gp<String> s2 = String::copy(", ");
|
|
gp<String> s3 = String::copy("world!");
|
|
gp<List> list = List::cons(s1, List::cons(s2, List::cons(s3, List::nil)));
|
|
|
|
// tell GC what to preserve
|
|
gc->add_gc_root(reinterpret_cast<Object **>(list.ptr_address());
|
|
|
|
gc->enable_gc(); // triggers immediate gc
|
|
|
|
// s1, s2, s3 invalid.
|
|
// list at new address
|
|
|
|
std::cout << "list.size=" << list->size << std::endl;
|
|
}
|
|
|
|
GC-Eligible Types
|
|
-----------------
|
|
|
|
Or, how to inherit ``xo::Object`` and provide GC support
|
|
|
|
A type Foo that inherits ``xo::Object`` needs to provide overrides for Object methods ``_shallow_size()``,
|
|
``_shallow_copy()`` and ``_forward_children()``:
|
|
|
|
Typical Pattern
|
|
^^^^^^^^^^^^^^^
|
|
|
|
GC support methods look something like this:
|
|
|
|
* class definition
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
#include "xo/alloc/Object.hpp"
|
|
|
|
namespace xo {
|
|
class Foo : public xo::Object {
|
|
public:
|
|
...
|
|
virtual std::size_t _shallow_size() const override;
|
|
virtual Object * _shallow_copy() const override;
|
|
virtual std::size_t _forward_children() override;
|
|
};
|
|
}
|
|
|
|
* use overloaded ``operator new``
|
|
|
|
A GC-eligible class will allocate instances using the ``MMPtr`` overload.
|
|
This allocates memory in GC-owned space
|
|
|
|
.. code-block::cpp
|
|
:linenos:
|
|
|
|
gp<Foo> Foo::make(...) {
|
|
...
|
|
return new MMPtr(mm) Foo(...);
|
|
}
|
|
|
|
* ``_shallow_size()`` returns the amount of memory used by the subject:
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
std::size_t Foo::_shallow_size() const { return sizeof(Foo); }
|
|
|
|
* ``_shallow_copy()`` is invoked during GC to create a copy of the subject
|
|
|
|
It should use the ``xo::Cpof`` argument to ``operator new``.
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
Object *
|
|
Foo::_shallow_copy() const;
|
|
|
|
* ``_forward_children()`` is invoked during GC to vist child ``xo::Object`` pointers
|
|
to make sure they survive
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
std::size_t
|
|
Foo::_forward_children();
|
|
|
|
Atomic Types Without Object Pointers
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Plain-old-data classes without embedded pointers
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
Object *
|
|
Foo::_shallow_copy() const {
|
|
return new (Cpof(this)) Foo(*this);
|
|
}
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
std::size_t
|
|
Foo::_forward_children() { return Foo::_shallow_size(); }
|
|
|
|
For example see ``xo::obj::String`` in ``xo-object``
|
|
|
|
Non-GC Objects
|
|
^^^^^^^^^^^^^^
|
|
|
|
A class *Foo* that inherits ``xo::Object`` can opt-out of garbage collection by
|
|
omitting the ``MMptr(mm)`` overload.
|
|
|
|
In that case `Foo::_shallow_size()`, `Foo::_shallow_copy()` and `Foo::_forward_children()`
|
|
will not be called:
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
std::size_t Foo::_shallow_size() const { return sizeof(Foo); }
|
|
Object * Foo::_shallow_copy() const { assert(false); return nullptr; }
|
|
std::size_t Foo::_forward_children() { assert(false); return 0; }
|
|
|
|
For example see ``xo::obj::Boolean`` in ``xo-object``
|
|
|
|
Structs Containing Object Pointers
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
A class with object pointers needs to tell GC how to traverse them
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
#include "xo/alloc/Object.hpp"
|
|
|
|
namespace xo {
|
|
class Foo : public xo::Object {
|
|
public:
|
|
...
|
|
virtual std::size_t _shallow_size() const override;
|
|
virtual Object * _shallow_copy() const override;
|
|
virtual std::size_t _forward_children() override;
|
|
|
|
private:
|
|
gp<Object> bar_;
|
|
gp<Object> quux_;
|
|
};
|
|
}
|
|
|
|
* ``_forward_children()`` is invoked during GC to fixup child pointers
|
|
that refer to forwarding objects:
|
|
|
|
.. code-block:: cpp
|
|
:linenos:
|
|
|
|
std::size_t
|
|
Foo::_forward_children()
|
|
{
|
|
Object::_forward_inplace(bar_);
|
|
Object::_forward_inplace(quux_);
|
|
|
|
return Foo::_shallow_size();
|
|
}
|
|
|
|
For example see ``xo::obj::List`` in ``xo-object``
|