1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-20 11:40:18 +02:00

Rename "whippet" collector to "mmc": mostly marking collector

This commit is contained in:
Andy Wingo 2024-09-02 13:18:07 +02:00
parent cf129f10de
commit 44a7240e16
10 changed files with 133 additions and 124 deletions

View file

@ -5,21 +5,21 @@ COLLECTORS = \
scc \
pcc \
\
whippet \
stack-conservative-whippet \
heap-conservative-whippet \
mmc \
stack-conservative-mmc \
heap-conservative-mmc \
\
parallel-whippet \
stack-conservative-parallel-whippet \
heap-conservative-parallel-whippet \
parallel-mmc \
stack-conservative-parallel-mmc \
heap-conservative-parallel-mmc \
\
generational-whippet \
stack-conservative-generational-whippet \
heap-conservative-generational-whippet \
generational-mmc \
stack-conservative-generational-mmc \
heap-conservative-generational-mmc \
\
parallel-generational-whippet \
stack-conservative-parallel-generational-whippet \
heap-conservative-parallel-generational-whippet
parallel-generational-mmc \
stack-conservative-parallel-generational-mmc \
heap-conservative-parallel-generational-mmc
DEFAULT_BUILD := opt
@ -70,28 +70,28 @@ GC_CFLAGS_scc = -DGC_PRECISE_ROOTS=1
GC_STEM_pcc = pcc
GC_CFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1
define whippet_variant
GC_STEM_$(1) = whippet
define mmc_variant
GC_STEM_$(1) = mmc
GC_CFLAGS_$(1) = $(2)
endef
define generational_whippet_variants
$(call whippet_variant,$(1)whippet,$(2))
$(call whippet_variant,$(1)generational_whippet,$(2) -DGC_GENERATIONAL=1)
define generational_mmc_variants
$(call mmc_variant,$(1)mmc,$(2))
$(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1)
endef
define parallel_whippet_variants
$(call generational_whippet_variants,$(1),$(2))
$(call generational_whippet_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1)
define parallel_mmc_variants
$(call generational_mmc_variants,$(1),$(2))
$(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1)
endef
define trace_whippet_variants
$(call parallel_whippet_variants,,-DGC_PRECISE_ROOTS=1)
$(call parallel_whippet_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1)
$(call parallel_whippet_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1)
define trace_mmc_variants
$(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1)
$(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1)
$(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1)
endef
$(eval $(call trace_whippet_variants))
$(eval $(call trace_mmc_variants))
# $(1) is the benchmark, $(2) is the collector configuration
make_gc_var = $$($(1)$(subst -,_,$(2)))

View file

@ -12,50 +12,59 @@ allocation, and provides a number of implementations of that API.
See the [documentation](./doc/README.md).
## Features
- Per-object pinning (with `mmc` collectors)
- Finalization (supporting resuscitation)
- Ephemerons (except on `bdw`, which has a polyfill)
- Conservative roots (optionally with `mmc` or always with `bdw`)
- Precise roots (optionally with `mmc` or always with `semi` / `pcc` /
`scc`)
- Precise embedder-parameterized heap tracing (except with `bdw`)
- Conservative heap tracing (optionally with `mmc`, always with `bdw`)
- Parallel tracing (except `semi` and `scc`)
- Parallel mutators (except `semi`)
- Inline allocation / write barrier fast paths (supporting JIT)
- One unified API with no-overhead abstraction: switch collectors when
you like
## Source repository structure
* [api/](./api/): The user-facing API. Also, the "embedder API"; see
the [manual](./doc/manual.md) for more.
* [doc/](./doc/): Documentation, such as it is.
* [src/](./src/): The actual GC implementation. The specific
implementations of the Whippet API are [`semi.c`](./src/semi.c), a
semi-space collector; [`bdw.c`](./src/bdw.c), the third-party
[BDW-GC](https://github.com/ivmai/bdwgc) conservative parallel
stop-the-world mark-sweep segregated-fits collector with lazy
sweeping; and [`whippet.c`](./src/whippet.c), the whippet collector.
* [src/](./src/): The actual GC implementation, containing a number of
collector implementations. The embedder chooses which collector to
use at compile-time. See the [documentation](./doc/collectors.md)
for more on the different collectors (`semi`, `bdw`, `scc`, `pcc`,
and the different flavors of `mmc`).
* [benchmarks/](./benchmarks/): Benchmarks. A work in progress.
* [test/](./test/): A dusty attic of minimal testing.
## To do
## Status and roadmap
### Missing features before Guile can use Whippet
As of September 2024, Whippet is almost feature-complete. The main
missing feature is dynamic heap growth and shrinkage
(https://github.com/wingo/whippet/issues/5), which should land soon.
- [X] Pinning
- [X] Conservative stacks
- [X] Conservative data segments
- [ ] Heap growth/shrinking
- [ ] Debugging/tracing
- [X] Finalizers
- [X] Weak references / weak maps
After that, the next phase on the roadmap is support for tracing, and
some performance noodling.
### Features that would improve Whippet performance
- [X] Immix-style opportunistic evacuation
- ~~[ ] Overflow allocation~~ (should just evacuate instead)
- [X] Generational GC via sticky mark bits
- [ ] Generational GC with semi-space nursery
- [ ] Concurrent marking with SATB barrier
Once that is done, the big task is integrating Whippet into the [Guile
Scheme](https://gnu.org/s/guile) language run-time, replacing BDW-GC.
Fingers crossed!
## About the name
It sounds better than WIP (work-in-progress) garbage collector, doesn't
it? Also apparently a whippet is a kind of dog that is fast for its
size. It would be nice if whippet-gc turns out to have this property.
size. It would be nice if the Whippet collectors turn out to have this
property.
## License
```
Copyright (c) 2022-2023 Andy Wingo
Copyright (c) 2022-2024 Andy Wingo
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the

View file

@ -1,5 +1,5 @@
#ifndef WHIPPET_ATTRS_H
#define WHIPPET_ATTRS_H
#ifndef MMC_ATTRS_H
#define MMC_ATTRS_H
#include "gc-config.h"
#include "gc-assert.h"
@ -61,4 +61,4 @@ static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) {
return GC_SAFEPOINT_MECHANISM_COOPERATIVE;
}
#endif // WHIPPET_ATTRS_H
#endif // MMC_ATTRS_H

View file

@ -7,14 +7,14 @@
threads. We analytically compute the peak amount of live data and
then size the GC heap as a multiplier of that size. It has a peak
heap consumption of 10 MB or so per mutator thread: not very large.
At a 2x heap multiplier, it causes about 30 collections for the
whippet collector, and runs somewhere around 200-400 milliseconds in
At a 2x heap multiplier, it causes about 30 collections for the `mmc`
collector, and runs somewhere around 200-400 milliseconds in
single-threaded mode, on the machines I have in 2022. For low thread
counts, the GCBench benchmark is small; but then again many Guile
processes also are quite short-lived, so perhaps it is useful to
ensure that small heaps remain lightweight.
To stress Whippet's handling of fragmentation, we modified this
To stress `mmc`'s handling of fragmentation, we modified this
benchmark to intersperse pseudorandomly-sized holes between tree
nodes.

View file

@ -1,19 +1,14 @@
# Whippet collector
# Mostly-copying collector
One collector implementation in the Whippet garbage collection library
is also called Whippet. Naming-wise this is a somewhat confusing
situation; perhaps it will change.
Anyway, the `whippet` collector is mainly a mark-region collector,
inspired by
The `mmc` collector is mainly a mark-region collector, inspired by
[Immix](http://users.cecs.anu.edu.au/~steveb/pubs/papers/immix-pldi-2008.pdf).
To a first approximation, Whippet is a whole-heap Immix collector with a
To a first approximation, `mmc` is a whole-heap Immix collector with a
large object space on the side.
When tracing, `whippet` mostly marks objects in place. If the heap is
When tracing, `mmc` mostly marks objects in place. If the heap is
too fragmented, it can compact the heap by choosing to evacuate
sparsely-populated heap blocks instead of marking in place. However
evacuation is strictly optional, which means that `whippet` is also
evacuation is strictly optional, which means that `mmc` is also
compatible with conservative root-finding, making it a good replacement
for embedders that currently use the [Boehm-Demers-Weiser
collector](./collector-bdw.md).
@ -33,7 +28,7 @@ recycled block from the global block store, it allocates into those
holes. For an exposition of Immix, see the lovely detailed [Rust
implementation](http://users.cecs.anu.edu.au/~steveb/pubs/papers/rust-ismm-2016.pdf).
The essential difference of `whippet` from Immix stems from a simple
The essential difference of `mmc` from Immix stems from a simple
observation: Immix needs a side table of line mark bytes and also a mark
bit or bits in each object (or in a side table). But if instead you
choose to store mark bytes instead of bits (for concurrency reasons) in
@ -54,13 +49,13 @@ just read the mark table and can avoid looking at object memory.
## Optional features
The `whippet` collector has a few feature flags that can be turned on or
The `mmc` collector has a few feature flags that can be turned on or
off. If you use the [standard embedder makefile include](../embed.mk),
then there is a name for each combination of features: `whippet` has no
additional features, `parallel-whippet` enables parallel marking,
`parallel-generational-whippet` enables generations,
`stack-conservative-parallel-generational-whippet` uses conservative
root-finding, and `heap-conservative-parallel-generational-whippet`
then there is a name for each combination of features: `mmc` has no
additional features, `parallel-mmc` enables parallel marking,
`parallel-generational-mmc` enables generations,
`stack-conservative-parallel-generational-mmc` uses conservative
root-finding, and `heap-conservative-parallel-generational-mmc`
additionally traces the heap conservatively. You can leave off
components of the name to get a collector without those features.
Underneath this corresponds to some pre-processor definitions passed to
@ -68,7 +63,7 @@ the compiler on the command line.
### Generations
Whippet supports generational tracing via the [sticky mark-bit
`mmc` supports generational tracing via the [sticky mark-bit
algorithm](https://wingolog.org/archives/2022/10/22/the-sticky-mark-bit-algorithm).
This requires that the embedder emit [write
barriers](https://github.com/wingo/whippet/blob/main/doc/manual.md#write-barriers);
@ -84,7 +79,7 @@ two-megabyte aligned slabs.
### Parallel tracing
You almost certainly want this on! `parallel-whippet` uses a the
You almost certainly want this on! `parallel-mmc` uses a the
[fine-grained work-stealing parallel tracer](../src/parallel-tracer.h).
Each trace worker maintains a [local queue of objects that need
tracing](../src/local-worklist.h), which currently has a capacity of
@ -96,17 +91,17 @@ then will try to steal from other workers.
The memory used for the external worklist is dynamically allocated from
the OS and is not currently counted as contributing to the heap size.
If you absolutely need to avoid dynamic allocation during GC, `whippet`
(even serial whippet) would need some work for your use case, to
allocate a fixed-size space for a marking queue and to gracefully handle
mark queue overflow.
If you absolutely need to avoid dynamic allocation during GC, `mmc`
(even `serial-mmc`) would need some work for your use case, to allocate
a fixed-size space for a marking queue and to gracefully handle mark
queue overflow.
### Conservative stack scanning
With `semi` and `pcc`, embedders must precisely enumerate the set of
*roots*: the edges into the heap from outside. Commonly, roots include
global variables, as well as working variables from each mutator's
stack. Whippet can optionally mark mutator stacks *conservatively*:
stack. `mmc` can optionally mark mutator stacks *conservatively*:
treating each word on the stack as if it may be an object reference, and
marking any object at that address.
@ -124,7 +119,7 @@ place roots in traceable locations published to the garbage collector.
And the [performance question is still
open](https://dl.acm.org/doi/10.1145/2660193.2660198).
Anyway. Whippet can scan roots conservatively. Those roots are pinned
Anyway. `mmc` can scan roots conservatively. Those roots are pinned
for the collection; even if the collection will compact via evacuation,
referents of conservative roots won't be moved. Objects not directly
referenced by roots can be evacuated, however.
@ -133,14 +128,14 @@ referenced by roots can be evacuated, however.
In addition to stack and global references, the Boehm-Demers-Weiser
collector scans heap objects conservatively as well, treating each word
of each heap object as if it were a reference. Whippet can do that, if
of each heap object as if it were a reference. `mmc` can do that, if
the embedder is unable to provide a `gc_trace_object` implementation.
However this is generally a performance lose, and it prevents
evacuation.
## Other implementation tidbits
`whippet` does lazy sweeping: as a mutator grabs a fresh block, it
`mmc` does lazy sweeping: as a mutator grabs a fresh block, it
reclaims memory that was unmarked in the previous collection before
making the memory available for allocation. This makes sweeping
naturally cache-friendly and parallel.

View file

@ -7,7 +7,7 @@ Whippet has five collectors currently:
but with support for multiple mutator threads.
- [Parallel copying collector (`pcc`)](./collector-pcc.md): Like `scc`,
but with support for multiple tracing threads.
- [Whippet collector (`whippet`)](./collector-whippet.md):
- [Mostly marking collector (`mmc`)](./collector-mmc.md):
Immix-inspired collector. Optionally parallel, conservative (stack
and/or heap), and/or generational.
- [Boehm-Demers-Weiser collector (`bdw`)](./collector-bdw.md):
@ -17,11 +17,11 @@ Whippet has five collectors currently:
## How to choose?
If you are migrating an embedder off BDW-GC, then it could be reasonable
to first go to `bdw`, then `stack-conservative-parallel-whippet`.
to first go to `bdw`, then `stack-conservative-parallel-mmc`.
If you have an embedder with precise roots, use `pcc`. That will shake
out mutator/embedder bugs. Then if memory is tight, switch to
`parallel-whippet`, possibly `parallel-generational-whippet`.
`parallel-mmc`, possibly `parallel-generational-mmc`.
If you are aiming for maximum simplicity and minimal code size (ten
kilobytes or so), use `semi`.
@ -30,17 +30,16 @@ Only use `scc` if you are investigating GC internals.
If you are writing a new project, you have a choice as to whether to pay
the development cost of precise roots or not. If you choose to not have
precise roots, then go for `stack-conservative-parallel-whippet`
directly.
precise roots, then go for `stack-conservative-parallel-mmc` directly.
## More collectors
It would be nice to have a classic generational GC, perhaps using
parallel-whippet for the old generation but a pcc-style copying nursery.
`parallel-mmc` for the old generation but a pcc-style copying nursery.
Support for concurrent marking in `whippet` would be good as well,
perhaps with a SATB barrier. (Or, if you are the sort of person to bet
on conservative stack scanning, perhaps a retreating-wavefront barrier
Support for concurrent marking in `mmc` would be good as well, perhaps
with a SATB barrier. (Or, if you are the sort of person to bet on
conservative stack scanning, perhaps a retreating-wavefront barrier
would be more appropriate.)
Contributions are welcome, provided they have no more dependencies!

View file

@ -1,6 +1,6 @@
# Whippet and Guile
If the Whippet collector works out, it could replace Guile's garbage
If the `mmc` collector works out, it could replace Guile's garbage
collector. Guile currently uses BDW-GC. Guile has a widely used C API
and implements part of its run-time in C. For this reason it may be
infeasible to require precise enumeration of GC roots -- we may need to

View file

@ -21,7 +21,7 @@ for full details, but for a cheat sheet, you might do something like
this to copy Whippet into the `whippet/` directory of your project root:
```
git remote add whippet https://github.com/wingo/whippet-gc
git remote add whippet https://github.com/wingo/whippet
git fetch whippet
git merge -s ours --no-commit --allow-unrelated-histories whippet/main
git read-tree --prefix=whippet/ -u whippet/main
@ -92,7 +92,7 @@ that all "large" or potentially large objects have a flag bit reserved
for use of the garbage collector. A large object is one whose size
exceeds the `gc_allocator_large_threshold()` (see
[`gc-attrs.h`](../api/gc-attrs.h)), which is a collector-specific value.
Currently the only generational collector is the in-place Whippet
Currently the only generational collector is the in-place `mmc`
collector, whose large object threshold is 4096 bytes. The
`gc_object_set_remembered`, `gc_object_is_remembered_nonatomic`, and
`gc_object_clear_remembered_nonatomic` embedder functions manage the
@ -187,15 +187,10 @@ implementations of that API: `semi`, a simple semi-space collector;
`pcc`, a parallel copying collector (like semi but multithreaded);
`bdw`, an implementation via the third-party
[Boehm-Demers-Weiser](https://github.com/ivmai/bdwgc) conservative
collector; and `whippet`, an Immix-like collector.
There is a bit of name overloading between the Whippet abstract API, the
collection of GC implementations, and the specific Whippet collector;
our apologies. It's just like that, and we hope to make the usage
obvious from context.
collector; and `mmc`, a mostly-marking collector inspired by Immix.
The program that embeds Whippet selects the collector implementation at
build-time. In the case of the specific Whippet collector, the program
build-time. In the case of the `mmc` collector, the program
also configures a specific collector mode, again at build-time:
generational or not, parallel or not, stack-conservative or not, and
heap-conservative or not. It may be nice in the future to be able to
@ -353,15 +348,26 @@ $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 \
-include foo-embedder.h -o gc.o -c bdw.c
```
#### Building `whippet`
#### Building `pcc`
Finally, there is the whippet collector. It can collect roots precisely
or conservatively, trace precisely or conservatively, be parallel or
not, and be generational or not.
The parallel copying collector is like `semi` but better in every way:
it supports multiple mutator threads, and evacuates in parallel if
multiple threads are available.
```
$(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE_ROOTS=1 \
-include foo-embedder.h -o gc.o -c pcc.c
```
#### Building `mmc`
Finally, there is the mostly-marking collector. It can collect roots
precisely or conservatively, trace precisely or conservatively, be
parallel or not, and be generational or not.
```
$(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 \
-include foo-embedder.h -o gc.o -c whippet.c
-include foo-embedder.h -o gc.o -c mvv.c
```
### Compiling your program
@ -370,12 +376,12 @@ Any compilation unit that uses the GC API should have the same set of
compile-time options defined as when compiling the collector.
Additionally those compilation units should include the "attributes"
header for the collector in question, namely `semi-attrs.h`,
`bdw-attrs.h`, or `whippet-attrs.h`. For example, for parallel
generational whippet, you might have:
`bdw-attrs.h`, `pcc-attrs.h`, or `mmc-attrs.h`. For example, for
parallel generational mmc, you might have:
```
$(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 \
-include whippet-attrs.h -o my-program.o -c my-program.c
-include mmc-attrs.h -o my-program.o -c my-program.c
```
### Linking the collector into your program
@ -462,7 +468,7 @@ defined for all collectors:
You can set these options via `gc_option_set_int` and so on; see
[`gc-options.h`](../api/gc-options.h). Or, you can parse options from
strings: `heap-size-policy`, `heap-size`, `maximum-heap-size`, and so
trings: `heap-size-policy`, `heap-size`, `maximum-heap-size`, and so
on. Use `gc_option_from_string` to determine if a string is really an
option. Use `gc_option_parse_and_set` to parse a value for an option.
Use `gc_options_parse_and_set_many` to parse a number of comma-delimited

View file

@ -48,28 +48,28 @@ GC_CFLAGS_scc = -DGC_PRECISE_ROOTS=1
GC_STEM_pcc = pcc
GC_CFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1
define whippet_variant
GC_STEM_$(1) = whippet
define mmc_variant
GC_STEM_$(1) = mmc
GC_CFLAGS_$(1) = $(2)
endef
define generational_whippet_variants
$(call whippet_variant,$(1)whippet,$(2))
$(call whippet_variant,$(1)generational_whippet,$(2) -DGC_GENERATIONAL=1)
define generational_mmc_variants
$(call mmc_variant,$(1)mmc,$(2))
$(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1)
endef
define parallel_whippet_variants
$(call generational_whippet_variants,$(1),$(2))
$(call generational_whippet_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1)
define parallel_mmc_variants
$(call generational_mmc_variants,$(1),$(2))
$(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1)
endef
define trace_whippet_variants
$(call parallel_whippet_variants,,-DGC_PRECISE_ROOTS=1)
$(call parallel_whippet_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1)
$(call parallel_whippet_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1)
define trace_mmc_variants
$(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1)
$(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1)
$(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1)
endef
$(eval $(call trace_whippet_variants))
$(eval $(call trace_mmc_variants))
gc_var = $($(1)$(subst -,_,$(2)))
gc_impl = $(call gc_var,GC_STEM_,$(1)).c

View file

@ -26,7 +26,7 @@
#include "serial-tracer.h"
#endif
#include "spin.h"
#include "whippet-attrs.h"
#include "mmc-attrs.h"
#define LARGE_OBJECT_THRESHOLD 8192