3

引用计数删除东西似乎比标记和清除垃圾收集器快得多,因为一旦不再使用,就可以释放东西并回收内存。mark-and-sweep 旨在解决的问题是捕获循环引用,但作为交换,您必须遍历整个对象树,并且在发生这种情况时其他一切都必须暂停。

仅在内存不足时保持引用计数并定期使用标记和清除不是更好吗?Mark-and-sweep GC 暂停是一个很大的痛苦,而且很难预测或避免。如果引擎也支持引用计数,它可能会大大减少对它们的需求——如果你小心避免循环引用,甚至可以减少到零。

我注意到Python 使用了这种方案,但可能更多是出于历史原因,而不是作为刻意的性能决定。

4

1 回答 1

3

这是一个艰难的决定,最好的选择并不总是明确的。Refcounting 确实有优点,但它并不像您所说的那样“明显”更好:

  • 真正的跟踪收集器是分代、增量和并发的某种组合。大多数时候,分代 GC 将“扫描整个堆”变成“扫描堆的一小块区域”,增量和并发 GC 消除了大停顿,理想情况下,许多停顿非常短,几乎无法测量。
  • 在它变成垃圾的那一刻释放内存并不总是最快的选择。分配通常更快(只是增加一个指针),如果一次释放大量内存,则可以降低释放成本(每字节)。
  • 即使与某些跟踪收集器使用的写屏障相比,引用计数在每次重新分配指针时都需要做很多额外的工作。在像 JavaScript 这样的语言中,这意味着引用计数可能会命中一个对象 50 次,而跟踪只会命中一次。更智能的方案通过引入跟踪收集器的各个方面来避免一些引用计数操作。同样,上述跟踪收集器的优化引入了引用计数的各个方面。有关这种二元性的详细信息,以及可能对这个问题感兴趣的成本模型,请参阅Bacon 等人的 A Unified Theory of Garbage Collection 。
  • 引用计数受到多线程的影响,比跟踪更严重,因为更改引用计数必须是原子的,并且原子操作相对昂贵(至少一些内核间同步)。跟踪要么暂停修改器,要么是并发的,并从线程安全中获得其他好处。尽管 JavaScript 名义上是单线程的,但我对浏览器引擎内部的了解还不够多,无法知道这是否是一个问题。
于 2013-07-10T20:53:58.483 回答