The algorithmic complexity of a sweeping collector is identical to that of a reference counting collector that can also break cycles. See A Unified Theory of Garbage Collection at http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.146....
A reference counting collector that _cannot_ break cycles (like in Swift) might have lower algorithmic complexity. I'm not sure.
The biggest bone of contention with reference counting is the constant adjustment of the reference count of objects on the stack. OTOH, the bone of contention with sweeping collectors is constantly walking all extant objects, including ones not directly on the stack.
There are various optimizations you can make to one or the other or both; many arguments about how real-world scenarios tax one or the other model; and many arguments about which is easier to optimize. But at the end of the day reference counting is no worse in terms of algorithmic complexity than sweeping. What matters most will be the quality of implementation. Is the compiler smart enough to know that an object passed 3 routines deep doesn't really escape the stack scope? If it can do that, it can elide all the cost of either approach.
This is the plan with Swift and what makes it zippy.
bone of contention with sweeping collectors is constantly walking
The real problem is that you cannot determine when the program will pause. It makes interactive and soft realtime impossible with gc. (Hard realtime should not run a generic allocator at all.) You can't write anything but servers and unpleasantly unresponsive apps.
Android developers have to carefully jump through hoops to avoid allocating any RAM at all if they don't want to bug their users. For years when the chips were slow, ref counting was a major competitive advantage for iPhone. A hundred-billion dollar win.
And then there's system software. You can't afford a live, unpredictable runtime messing with you in systems code so anything that touches low level code can't use gc. There's a disincentive to really invest in a language that simply can't ever do a full stack, even if you're unlikely to need to write a device driver anytime soon. That's why no major browsers, performant ACID databases, or fast app servers depend on RAM gc.
I have trouble believing that behind-the-scenes escape analysis is very effective on real-world code. Look at how much painfully explicit source annotation Rust found they needed.
Like you said, need to update the reference every time it's moved.
malloc becomes much slower, compared to a GC language (with a compacting GC) where malloc is effectively just a single add instruction.
free also needs to do a lot of bookkeeping while the GC language doesn't need to do anything.
Multithreading introduces another set of issues when dealing with refcounting that is not a problem with a GC.
Reference counting is very predictable. And if you see a bottle neck you can start fixing it.
And with ARC (Swift/ObjC) you get even compiler support to optimize all ref counting administration.
Further malloc doesn't have anything to do with ref counting as ref counting sits on top if it.
Further, GC languages also need to do book keeping: Generations and Compaction.
For malloc, the benefit with a compacting GC is that the free memory will always be in a contiguous segment. Therefore, a malloc operating is similar to a stack allocation in that all you need to do is to move a single pointer. Without a compacting GC, you need to search a tree whenever you want to allocate memory.