4 ms·
> - no sharing across threads, otherwise locking is required for refcount updates That is not true. Most atomic refcount implementations are lockfree (you do
by Jweb_Guru 8y ago
> - no sharing across threads, otherwise locking is required for refcount updates
That is not true. Most atomic refcount implementations are lockfree (you do need synchronization though). This optimization has nothing to do with tracing. Given that you also need synchronization for tracing garbage collection (including frequent stop the world pauses in most cases, albeit brief ones), I don't think this is even really an advantage of tracing at all.
> - no deep datastructures, otherwise the call stack of cascading deletions will produce a stop similar to tracing GC
If your program has data structures that live through several collections, refcounting can be faster than tracing GC because it only traces objects once (when they die) instead of several times. Additionally, you can defer the refcount updates in ways that avoid the need to do lots of work on deallocation, and optimize for short-lived objects, to get many of the effects of generational GC. This also has little to do with tracing (except inasmuch as a reference counter with this optimization has to "trace" new objects to make them initially live, but in some sense this work to update the reference counter for the first time is just moved from object initialization time to a later point in the program). The reason it's not usually done is that it requires precise liveness information by an ambient collector, which complicates the implementation, but "exploiting liveness information" is not the same as "being a tracing GC."
> - implementations need to be clever about nested deletions when refcount reaches 0, otherwise a stack overflow might happen
This is extremely trivial to avoid (if you need to) and has nothing to do with tracing. It's really more a product of user-defined destructors than anything else, which you don't need to provide in order to implement reference counting. In fact, a lot of the supposedly inevitable slowness of reference counting compared to tracing goes away when you ban nontrivial destructors (and conversely, nontrivial destructors make tracing perform much worse).
> This is only relevant for naive refcount implementations, there are many optimisations, which endup turning a refcounting implementation into a tracing GC in disguise.
The most optimized versions of refcounting I'm aware of get their wins from things like precise knowledge about live references and update coalescing--not tracing, except for some young object optimizations as I alluded to above which are more about satisfying the generational hypothesis (they generally have a backup tracer in order to break cycles, but if you're willing to live without that they usually don't need tracing to reclaim all memory). Many optimizations people commonly associate with tracing (e.g. cache friendliness due to compaction) are in practice only relevant for young generations most of the time; for older ones both tracing and reference counted implementations tend to benefit more from a really smart allocator with intelligent memory layout and partial reclamation.
I agree that optimized versions of refcounting are difficult and the fact that you still need a backup tracer for most code discourages people from using it, as well as that tracing doesn't negate a lot of systems optimizations. But a lot of the stuff you're saying is pretty misleading: the things that make tracing efficient can mostly be applied to make reference counting efficient without "turning it into tracing," with the cost that optimized tracing and optimized reference counting both need much more invasive knowledge about the user program (and correspondingly restrict the users) compared to the less optimized versions.
- kjeetgill 8y ago> The most optimized versions of refcounting I'm aware of get their wins from things like precise knowledge about live references and update coalescing- I know Python, objective-c, and swift all use refcounting, but just for my own reading, what language has the most optimized ref counting right now?
- Jweb_Guru 8y agoThe most optimized version I'm aware of is the RCImmix collector for Java: http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rcix-oopsla-2013.pdf http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rcix-oops.... It achieved performance parity with the regular Immix collector, which was the fastest (in the sense of throughput) available for the Jikes research JVM at the time. A backup collector is required for two reasons: cycle collection, and resetting stuck counts (they gain significant performance wins from keeping only a few bits for the actual refcount, for a variety of reasons: usually only a small percentage of objects need more, and they tend to be the most heavily accessed objects, so they save a lot of time by avoiding updates to them and also gain from being able to reuse objects' existing metadata fields rather than use extra space for the refcount). It's worth noting that most recent work on tracing GCs has been on reducing pause times, not improving throughput, so it wouldn't shock me if Immix is still the state of the art there. There may have been further developments in improving RC throughput since 2013, but I'm not familiar with them; outside of Apple, there's very little modern research into optimizing reference counting. I know Swift does many cool optimizations of its own, but I'm not sure how restricted they are by having to remain compatible with Objective C.
- dllthomas 8y agoWith refcount you need to sync every time you add/remove a reference to an object. For many (most?) workloads that's going to be quite substantially more frequent than tracing collections. Whether one or the other wins out probably depends a lot on your use case. More generally, I think the parent comment was not aiming to say that reference counting is never appropriate, but that blindly going with reference counting because it's now "ew, GC" is misguided.