Treadmill garbage collector by H. Baker
cofault.com
cofault.com
I wrote a lot of code for CADR, Symbolics and D-Machine Lispms and I’m not aware of any of them having hardware or microcode GC. That would be crazy anyway. Tagging and other support sure, but you could say that the x86 has hardware support through its pager too.
As for stack frames on the heap, think of how important function calling is, and the impact of allocation, cache, etc of such an approach. They all used PDLs like any other machines.
Now InterLisp-D did spaghetti the stack when you made a closure, at least for some time. I know this because I triggered it with a pathological case of making thousands of closures, bringing any machine to its knees. But a spaghetti stack is a different thing from allocating frames from the heap.
yeah, I don’t think anyone looked at the phantom stack idea. Perhaps interlisp should have.
At the dawn of the Squeak Smalltalk era, there was some company (forget which) that did a hybrid Bakers treadmill implementation for some real time stuff.
I did a simplistic implementation in C once for an image processing pipeline to manage labeled regions. Once you get it correct, it’s pretty slick. I used a Fibonacci sizer for the different ring sizes.
I'm just hearing of the Treadmill for the first time, so I can't compare the design in detail yet, but the two pictures tell a lot of that story.
In either case that doesn't seem quite as useful as the claimed "alloc is just a pointer chase" and not much better than other GCs that don't claim to be real-time, and makes it soft real-time (upper bound on instructions) not real-time (exact number of instructions); most other incremental GCs also have a bounded set of work they do per operation.
It also sounds like it requires a mutex on read barriers for multithreaded programs, which is pretty bad and expensive. Most modern GC read barriers are just setting a bit in a card table, which doesn't require a lock.
Off the top of my head I think most non-moving GCs use only write barriers and not read barriers now, though, along with writes being less common than reads, which is maybe even better than what I said. https://webkit.org/blog/7122/introducing-riptide-webkits-ret... for example only uses a write barrier, and so does Lua's tricolor GC.
The closest modern analogue would maybe be something like Metronome, which is a moving GC that also advertises as real-time, but has a read barrier they intentionally tried to make very cheap and optimizable - I guess you could also coalesce Treadmill read barriers to only one mutex + color transition for multiple fields of an object all at once, though.
The heap size you need is R*(1+1/k). So if k=1, then you need to threat a half-full heap as an out-of-memory condition, which is comparable to quite a few GC's. If you set k at e.g. 4 instead, you can use 80% of the heap.
If you're ok with losing real-time guarantees you can instead dynamically increase k as needed.
The original description is for CONS cells, which have exactly 2 pointers; the notes on generalization later in TFA state that if you want to support arbitrary sized objects, advance() needs to be made incremental for larger objects.
but definitely a must-read if you care about that kind of thing
[0] https://lamport.azurewebsites.net/pubs/garbage.pdf
[1] https://plover.com/~mjd/misc/hbaker-archive/RealTimeGC.html
The GC would move used objects to the front (as if they were running forward). The unused objects would either be moved (or left in place) to the back (as if they stopped running). All memory in the back would be considered free space when alloc-ing.
But the article quickly says thats not going to happen since its "in-place" and I have no idea what my idea would be already called.
I bet there isn't a language around (with GC) that tops their GC implementations.
For Java,
https://docs.oracle.com/en/java/javase/18/docs/specs/jni/des...
https://github.com/openjdk/jdk/blob/master/src/java.base/sha...
And as bonus since you didn't ask for it, Eiffel
In Go, objects are segregated by allocation size, so the GC can round subobject pointers to keep the surrounding object alive.
C# has Span<T>, nowadays, but is guaranteed to only live on the stack, making it easier for the GC to keep the object it points into alive while the Span lives (https://docs.microsoft.com/en-us/dotnet/api/system.span-1?vi...)
All provide ways to shoot yourself if not used properly.
You either use one treadmill with a fixed size abject header/pointer. The “guts” of the object are placed on a heap which gets spaghettified and fragmented pretty fast, especially if your not going to move bodies, because… real time.
The other approach is to use multiple treadmills/rings for different size objects. Different rings are then used for different sizes of objects. In this way, your heap fragmentation/locality improves, but at the added cost of indirecting on object size.
Languages with only one object size are not exactly in vogue at the moment, so this is a major limitation.
No, it doesn't, as it maintains linked lists of objects, so you can use any underlying allocator to feed you objects to add to the different linked lists.
No old objects containing pointers to new objects and no mutation while scanning hazards.