Main reasons for me:
- Reference counting is deterministic (barring refcnt loops, which can usually be avoided by a careful programmer). Each run of the program will allocate/free memory in _exactly_ the same way.
- No latency spikes. "When GC kicks in, the latency must raise"
- This means it's way easier to understand the performance characteristics of code.
Having that in mind, there are couple of problems with refcnt:
- Cache spill. Increasing/decreasing a counter values in random places in memory kills cache. I remember one time I separated the "string object" data structure in python away from the immutable (!) string value (python has usually C struct object followed in memory by value blob). The python program suddenly went _way_ faster - immutable (readonly) string values in memory were close to each other, mutated refcnts were close to another. Everyone was happier
- When dealing with large data (binary blobs), refcnt programming languages have a tendency to copy over needed data. Copying over is in many cases slower.
In general I'd say it's the last point that is critical. The big question is less of "refcnt" vs "gc", but how to deal with binary blobs. "Copy over" pattern (C style) and newer version: "slices" (golang style) VS "proper GC" style of trying to guess how may pointers point to a blob.