3 ms·
> Riding the fence leads to a worse experience for both sides. I don't entirely agree, I think having a garbage collected pointer (GCP) can make a lot of sense
by masklinn 4y ago
> Riding the fence leads to a worse experience for both sides.
I don't entirely agree, I think having a garbage collected pointer (GCP) can make a lot of sense as a performance optimisation: manual memory management (MMM) and reference counting (RC) have stampeding characteristics similar to GC pauses when releasing large hierarchies e.g. persistent data structures, or large trees of widgets: all the tree is freed synchronously and recursively, if it's large it can be very sensible.
This stampeding is more predictable than a GC pause, but it's no less problematic when it occurs, and mitigating it can be difficult (you have to manually shunt the objects you'd like to release off-thread). A GCP can do that shunting on its own, letting a GC thread perform the releases asynchronously, and piecemeal.
Furthermore, GCP means you're not expecting to precisely track allocations at the system level, which means the usual tricks (arenas, bump allocators, ...) can be applied by default to GC pointers, where they usually can't be to MMM or RC pointers. This decreases allocation and deallocation overhead by reducing the amount of work the system has to do.
- pjmlp 4y agoHence why other languages do make this differentiation, only D refuses to go down this path.
- zozbot234 4y ago> you have to manually shunt the objects you'd like to release off-thread. A GCP can do that shunting on its own, letting a GC thread perform the releases asynchronously, and piecemeal. You could also use async programming to do the same thing in GC-free code. Or just use an arena that can be freed all at once.
- masklinn 4y ago> You could also use async programming to do the same thing in GC-free code. No? Allocations are blocking, "async programming" won't do anything. Unless you have an async dealloc which does the shunting implicitly, at which point you don't need an async dealloc, you can just have a sync one which shunts the actual dealloc and mandate a background thread. Which means now you're mandating a background thread for freeing memory. And you hope that the load is light enough that implicit thread which you've tasked with all deallocations (rather than just the ones which make sense) keeps up. > Or just use an arena that can be freed all at once. That is essentially the same thing, you now have a different allocation strategy for these, except it's a lot more limited and specific.
- SleepyMyroslav 4y ago> No? Allocations are blocking, Modern malloc/free implementations must satisfy requirements such as being scalable. It eliminates any chance to implement blocking deallocation from non-owner threads. A modern implementation already has to be async in some ways. You can check paper behind Mimalloc for example.
- kaba0 4y agoAn arena allocator presupposes a huge number of objects each having the same lifecycle - which is not all that common/general.
- generichuman 4y ago> which is not all that common/general. Your objects in trees and arrays don't have the same lifecycle? Quoting GP: > ... reference counting (RC) have stampeding characteristics similar to GC pauses when releasing large hierarchies e.g. persistent data structures, or large trees of widgets: all the tree is freed synchronously and recursively, if it's large it can be very sensible. This is mostly where RC & MMM fails. This is also where you are supposed to use arena allocators. If you have a tree you're constantly adding to / deleting from, you can use a Pool backed by an Arena. At that point you don't use pointers, only indices into the pool. When you want to remove something from the tree, you mark the objects in the pool "deleted" until you want something added back again.
- masklinn 4y ago> Your objects in trees and arrays don't have the same lifecycle? No, there is a nested set of lifetimes. Take a hierarchy of widgets for instance, if you change a sub-view you're going to reclaim the widgets composing that sub-view, replacing them with widgets of an other sub-view. Then there's persistent datastructures, where lifetimes are reversed (higher nodes are younger), you update a node, you're going to invalidate all the nodes on the path to that, but the nodes which are not on that path are still alive. > If you have a tree you're constantly adding to / deleting from, you can use a Pool backed by an Arena. At that point you don't use pointers, only indices into the pool. When you want to remove something from the tree, you mark the objects in the pool "deleted" until you want something added back again. So you're implementing an ad-hoc GC by hand (and so does everyone else) and making your traversal more complicated. If there's a GC pointer provided (by the language or a library), in the vast majority of cases it will work well enough and you won't need to waste time on that, you can solve actual issues instead.
- kaba0 4y agoSure, it’s a great optimization opportunity — though I would argue that replacing pointers with indexes can be a bad thing (often seen in Rust when people don’t want to argue with the compiler), since then not even valgrind and the like can catch your “use-after-free” errors.