← Back to context

Comment by jjoonathan

13 years ago

If the ref counting is manual or optimized, it doesn't happen after every assignment, only ones which change object ownership. If you're really nuts about avoiding cache hits, you can pack most ref counts into the low bits of the class pointer, since allocations tend to be 16-byte aligned, including allocations for class objects. Assuming you use the object at least once following a change of ownership, the overall cost in cache misses becomes 0. Or you can store the references in a global table with better cache properties (IIRC this is what ObjC does). Or you can rely on the fact that cache lines are typically large enough to grab a few words at a time, so fetching the class pointer will automatically fetch the refcount (again with the assumption that you use objects at least once per assignment). Honestly, I'm having difficulty imagining how cache behavior could become a problem unless you wanted it to.

As for not allowing cycles, I consider that a feature. The headache of memory management doesn't go away with GC. You still have to avoid inadvertently keeping references to objects through the undo stack & so on. Unintentional strong refs creep through GC code just as easily as memory leaks creep through code with manual memory management. Almost universally, I find that GC'd projects large enough to have memory management best practices implicitly do away with this freedom at the first opportunity by calling for acyclic or single-parent object ownership graphs. These restrictions primarily make it easier to think about object lifecycle -- the fact that they allow refcounting to suffice for memory management is icing on the cake.

> If the ref counting is manual or optimized, it doesn't happen after every assignment, only ones which change object ownership.

The Rust compiler does this. Even so, 19% of the binary size in rustc is adjusting reference counts.

I am not exaggerating this. One-fifth of the code in the binary is sitting there wasted adjusting reference counts. This is much of the reason we're moving to tracing garbage collection.

All of those optimizations that you're talking about (for example, packing reference counts into the class pointer) will make adjusting reference counts take even more code size.

> As for not allowing cycles, I consider that a feature. The headache of memory management doesn't go away with GC.

The fact that memory management is still an issue doesn't mean that we shouldn't make it work as well as we can.

In large software projects, cycles are a persistent issue. Gecko had to add a backup cycle collector over its reference counting framework, and observed memory usage decreased significantly once it was implemented.

  • That's the evolution Python went through as well: reference counting, then (in CPython 2.0) an adjunct cycle-collector, then (in PyPy) a non-reference-counting garbage collector.

    Hopefully in future versions of PyPy an even better garbage collector. :-)

Very nice explanation, except you are forgetting WinRT is actually an extension of COM.

This means each increment/decrement is a virtual method call with the corresponding invocation costs.

This is why the .NET runtime caches the WinRT objects, and even re-uses them instead of making a 1:1 use like C++/CX does.