3
votes

Reference counting would seem to be much quicker to remove stuff than a mark-and-sweep garbage collector, as things can be freed and the memory recycled as soon as it is no longer used. The problem that mark-and-sweep aims to solve is catching circular references, but in exchange you have to walk the entire object tree, and everything else has to pause while this happens.

Wouldn't it be better to keep reference counting, and use mark-and-sweep periodically, only when memory is low? Mark-and-sweep GC pauses are a big pain and hard to predict or avoid. If the engine supported reference counting as well, it could possibly reduce the need for them a lot - even to zero if you are careful to avoid circular references.

I notice that Python uses this kind of scheme, but possibly more for historical reasons than as a deliberate performance decision.

1
which javascript implementation do you mean?Alex Shesterov
Are you having some particular performance problem or are you just curious? If the latter, then this really isn't the best place for the question. You'd be better off talking to some actual runtime implementation people from Mozilla or Google.Pointy

1 Answers

3
votes

This is a hard decision and the best option isn't always clear-cut. Refcounting does have merits, but it's not "obviously" better as you argue:

  • Real tracing collectors are some combination of generational, incremental, and concurrent. Generational GCs turn "scan the whole heap" into "scan a small region of the heap" most of the time, incremental and concurrent GCs remove the big pause in favor of, ideally, many pauses so short that they can barely be measured.
  • Freeing memory the instant it becomes garbage isn't always the fastest option. Allocation is typically quicker (just incrementing a pointer), and the cost of deallocation (per byte) can be reduced if lots of memory is freed at once.
  • Reference counting requires quite a bit of additional work on every pointer re-assignment, even compared to write barriers as used by some tracing collectors. In an language like JavaScript, this means refcounting might hit an object fifty times while tracing only hits it once. Smarter schemes avoid some refcount operations by introducing aspects of tracing collectors. On the same note, the aforementioned optimizations of tracing collectors introduce aspects of refcounting. See A Unified Theory of Garbage Collection by Bacon et al for details on this duality, as well as a cost model which may be of interest to this question.
  • Refcounts suffer from multi-threading, more so than tracing, because altering the refcount must be atomic and atomic operations are comparatively expensive (as least some inter-core synchronization). Tracing either pauses the mutators anyway, or is concurrent and gets other benefits out of being thread-safe. I do not know enough about browser engine internals to know if this is a problem even though JavaScript is nominally single-threaded.