Hacker News new | ask | show | jobs
by ryannielsen 5250 days ago
Since iOS5 there's refcounting

Actually, that's not true.

Since at least NeXTSTEP there's refcounting. In 1989, NeXTSTEP introduced their NSObject-based Objective-C runtime with relied on -retain and -release calls to count object references. I can only find records commenting on NSObject's support for -retain and -release, but it may have been done earlier in a different Objective-C based runtime. (And I'm certain refcounting was pioneered in earlier runtimes.)

In 1995, the OpenStep standard introduced autorelease pools, which helped automate reference counting. Calling -autorelease on an object increments the retain count and instructs the enclosing autorelease pool to decrement the count when it pops.

In 2011, Lion and iOS 5 introduced ARC – Automatic Reference Counting. It's actually a pretty sweet memory management solution where simple statically applied code transformations streamline memory management without the need of a garbage collector. As you note though, circular dependencies cannot (yet!) be handled by ARC. To assert that circular dependencies can only be handled through garbage collectors is possibly short-sighted. I know the team that implemented ARC already has prototypes that handle some kinds of circular dependencies, or warn programmers of possible circular deps in their code.

To say automatic ref counting is "far away" from garbage collection is rather disingenuous. It's simply different. Both have tradeoffs. Garbage collection handles all memory management for you; automatic reference counting still requires some manual memory management. GC gracefully deals with circular dependencies; ARC leaks memory with circular dependencies. GC requires runtime overhead in memory and clock cycles; ARC minimizes memory and CPU overhead and, when you need zero overhead, you can drop down to manual memory management.

Frankly, I far prefer ARC to GC. ARC gives me almost all the benefits with no runtime overhead. There's nothing running alongside my code that might randomly suck up cycles or pause execution, and I can always profile exactly what my users will run. Don't knock refcounting just because it's not vogue.

1 comments

Reference counting does have runtime overhead, though. Incrementing and decrementing references as a reference gets passed around isn't free. If you have many small objects, the reference count itself may become a significant memory overhead. Furthermore, GC means that memory allocation can be done by allocating at the top of the heap instead of a more expensive malloc, and freeing garbage may be literally free depending on the algorithm used. When an object falls out of scope in a reference counted world, it is typically immediately freed - if this is a handle into a large recursive data structure, for example, it can definitely suck up cycles. Counterintuitive as it may seem, GC can be the cheapest solution in some circumstances, unless your program eschews malloc entirely.
Yes, GC can be faster in some scenarios. I'll never get into a dispute about what's faster... legitimate arguments can be made both ways.

My point about profiling the exact user experience still stands. I can exactly instrument the hotspots in my code and fix them as needed – I can disable ARC, I can avoid autorelease pools. (Or I can add my own autorelease pools or manual retains, preventing large objects from being freed until I'm outside of a hotspot.) Importantly, I know what's being profiled is exactly what will be run by my users. I can't say the same about GC code, since the collector must be free to reap whenever and however it decides. Heck, depending on the runtime, I can't even be certain which GC algorithm will be used by my customers.

I appreciate the control refcounting gives me, and I love that ARC makes writing refcounted code almost as painless as writing GC code without the loss of control. I like being able to test and profile exactly what my users will run.

Another nice thing about refcounting versus other garbage collection strategies is that it can have more deterministic memory access patterns-- including possibly preventing heap fragmentation.