-
Notifications
You must be signed in to change notification settings - Fork 955
reconsider a compacting garbage collector #4909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The big issue with a moving GC is that interrupts can't run while the GC is moving memory. Or if they do, it's in a very limited way. Interrupts are kinda really important to run right away, without blocking for a GC. There might be a few (difficult) ways to make this possible:
So in other words, the only options I see to make this work either will make all code very slow (due to load/store barriers or similar overhead) or make interrupts really difficult to write. I don't see a way to avoid this. I've thought long and hard about this problem in the past, I just don't see how this can work. If you have any suggestions, I'm certainly open to new ideas. There are a few other problems with a moving GC though:
Our current GC is also really, really simple. I'm sure there are plenty of ways to make it work better. We currently just allocate objects sequentially until we run out of space and then do a GC cycle. Something simple as grouping allocated objects by size would probably help a lot to reduce fragmentation. Also, for some embedded systems with enough memory the Boehm GC might be worth using (see #4812 for example). |
Thank you for taking the time to write down the issues of a moving collector.
I think this is the most viable way. If interrupts are only allowed to touch non-moving memory, this problem goes away, right? If we then say that memory allocated by interp or That leaves runtime detection, which I agree is essential. Here's an idea:
This rests on the assumptions that normal code and interrupt code overlaps very little and that the call graphs of interrupts contain much less code than normal code. If those hold, I don't think this idea will bloat binaries by much more than the runtime checks themselves.
Or simply disallow C code when the non-moving collector is enabled.
Absolutely, but then again only when enabling the non-moving collector.
Absolutely. This issue is just about reconsidering a non-moving collector, implementation is another story.
That's unfortunate. A robust collector should be fully precise, for the same reason it should be compacting.
Reducing fragmentation is nice, but IMHO not good enough to fearlessly (to borrow a Rust concept) allocate in long-running TinyGo programs on memory constrained platforms.
Ditto. |
Thinking about this some more, I think if even a low-cost runtime check is not viable, a more expensive check is ok. That is, something like the Go race detector that enables slow runtime checks everywhere to detect accesses to unpinned memory from interrupt contexts. |
I'd be open to the addition of a moving GC (if it doesn't interfere too much), but probably won't be working on it myself.
We use C for a few important bits of code:
While some of these could be replaced with Go or assembly versions, it's not always possible or would be very difficult. Which means that we'd probably not be able to enable a moving GC by default. Which means it's much less tested and most people won't see the benefit of having a moving GC. Again, I'm not opposed to it, I just don't really see it as a viable option for TinyGo. But I could be proven wrong.
Possible, but most people won't be running this I'm afraid. |
Hang on, why is this the case? In Big Go, pointers passed to C(go) are either automatically pinned or must be pinned by
Perhaps not, but we'll run it in CI tests. Combined with the race detector and/or The situation is similar to the weird crashes that currently happen when a goroutine stack is overflowed. That would also be nice to have a runtime check for, which even if fairly expensive could be suggested to users reporting such crashes, and called out in the FAQ. |
Hmm, I think you're right. I was mainly thinking about C malloc and free, but I guess they can be implemented by a Go alloc and pinning the resulting memory. If you want to read more, I can recommend this book: https://gchandbook.org/ |
Extracted from #4889.
@aykevl wrote
I've seen this comment before, but I'd like to say I find the current garbage collector too unreliable for dynamic allocation because of memory fragmentation. I frequently run into out-of-memory panics before even half of available memory (512kB) has been filled because of fragmentation. Coupled with the escape analyzer missing key allocations I find myself spending significant effort (and API contortions) to avoid allocations altogether.
I don't see any other way of fixing fragmentation and making the GC reliable than making it moving (e.g. mark-compact GC).
In other words, please reconsider adding a garbage collector that is doesn't fail because of memory fragmentation.
I realize that non-GC runtimes such as the C runtime are also prone to memory fragmentation, which is why embedded programmers shy away from dynamic memory allocation. However, GC languages such as Go are uniquely positioned to eliminate memory fragmentation, thus guaranteeing robustness provided the dynamic heap size fits in available memory.
A practical implementation will need to support pinning of memory used for DMA and interrupts. The recently added
runtime.Pinner
was added to Big Go for such purposes. AddingPinner
to DMA and interrupt code is a challenge, but I think the trade-off is worth it: there's much more user code than DMA or interrupt code.The text was updated successfully, but these errors were encountered: