标记和扫描GC有什么问题?

RCI*_*CIX 8 performance garbage-collection

我正在阅读Steve Yegge的"动态语言反击"谈话,其中有点批评标记和扫描的GC(通过该链接大约5-10%,"Pigs尝试飞行"幻灯片)他们有什么问题?

Ste*_*n C 31

这是参考报价中提到的各种技术的高级子弹点比较(加上"标记 - 紧凑"......这是标记和扫描的变化.)

引用计数集的属性是:

  • PRO - 立即回收垃圾(除了周期)
  • PRO - 垃圾收集暂停较小,如果可以推迟更新"自由空间"数据结构,则最小.
  • CON - 需要在大多数指针写操作上调整引用计数
  • CON - 自由空间永远不会被压缩
  • CON - 因为自由空间没有被压缩,所以必须保持"自由空间"数据结构,这增加了分配和解除分配成本.
  • CON - 不收集循环垃圾,除非应用程序手动打破循环.
  • CON - 在多线程应用程序中更新引用计数是非常昂贵的.

对于经典的标记和扫描:

  • PRO - 没有指针写入开销
  • PRO - 收集循环数据
  • PRO - 存储管理并发瓶颈可以避免(除了GC)
  • CON - 停止世界垃圾收集
  • CON - 自由空间永远不会被压缩
  • CON - 因为自由空间没有被压缩,所以必须保持"自由空间"数据结构,这增加了分配和解除分配成本.

经典的标记和扫描有时会被修改,以便扫描阶段通过"滑动"非垃圾对象来压缩自由空间.这被称为"mark-sweep-compact".这很复杂但是:

  • PRO - 没有指针写入开销
  • PRO - 收集循环数据
  • PRO - 存储管理并发瓶颈可以轻松避免(除了GC)
  • CON - 停止世界垃圾收集
  • PRO - 免费空间被压缩,因此分配便宜
  • CON - 紧凑阶段相当昂贵

现代收藏家(包括典型的世代收藏家)基于标记和复制.这个想法是收集器在"从空间"中追踪对象,将它们复制到"空间".完成后,"to space"在末尾有一个连续的可用空间块,可用于分配新对象.旧的"从空间"放在一边,以便下次垃圾收集器运行.复制集合的好处是与垃圾对象相关的垃圾收集成本接近于零.

  • CON - 指针写入开销(记录"新一代"指针写入"老一代"对象时)
  • PRO - 收集循环数据
  • PRO - 存储管理并发瓶颈可以轻松避免(除了GC)
  • CON - stop-the-world垃圾收集,尽管这可以通过一些运行时开销来降低
  • PRO - 对于世代收集器,你通常GC只是堆的一部分,有很多垃圾,因此GC开销平均较少
  • PRO - 较小的GC暂停(大部分时间)
  • PRO - 免费空间被压缩,因此分配便宜
  • PRO - 压缩比使用滑动压缩机更便宜
  • CON - 您需要为收集器保留额外的对象空间.

世代收藏家是有多个空间(世代)的收集者,它们以不同的速度收集.这是基于这样的假设:大多数对象都是在短时间内创建的,然后无法访问.因此,通过垃圾收集包含年轻物体的空间,您可以以相对较低的成本回收相对大量的空间.你仍然需要收集老一代,但这种情况可能不那么频繁.

(标记和扫描收集器可以是世代的,但是回报并不像复制收集器那么大.)


Jon*_*eet 10

这是引用的上下文:

分代垃圾收集器是我得到的最好的答案,因为它减少了暂停,坦率地说,今天所有[新]动态语言的垃圾收集器都是垃圾.它们是标记和扫描,或者它们被引用计数.

从引用来看,他似乎在谈论相当原始的GC,这不是代际的.世代GC仍然可以进行标记和扫描,但是它们在大多数情况下标记的次数要少得多,这使得它们比"每次标记和扫描世界"要快得多.

假设这就是他的意思,我同意 - 但他可以说得更清楚.请记住,这是一个谈话而不是博士论文 - 提出最明确的方式表达自己"在蹄子上"是有点棘手的:)


Pav*_*aev 5

他将它与mark-compact对比:

分代垃圾收集器是我得到的最好的答案,因为它减少了暂停,坦率地说,今天所有[新]动态语言的垃圾收集器都是垃圾.它们是标记和扫描,或者它们被引用计数.

普通标记和扫描GC不是很好,因为它们存在堆碎片问题.由于支持GC的语言通常具有高分配级别,这通常比在例如C++中更快地出现问题,在C++中,许多对象只存在于堆栈中.

也就是说,mark-compact实际上是标记和扫描,其上有压缩,因此术语可能会更好.非压实收集器通常被称为"保守"以区分它们.