【问题标题】:How can I provide garbage collection for an interpreted language implemented in C?如何为用 C 实现的解释语言提供垃圾收集?
【发布时间】:2015-04-09 11:06:07
【问题描述】:

如果我要在 C 中实现垃圾收集解释型语言,我如何在不编写自己的垃圾收集器的情况下提供精确(即不保守)的垃圾收集?有可用的图书馆吗?如果有,有哪些?我知道,对于垃圾收集器跟踪的任何对象,我都必须在自己的一端维护某些不变量。

【问题讨论】:

  • 根据您的实现,计算对动态对象的引用。一旦它们达到零 - 丢弃。
  • 您可能想看看一些示例,例如 Lua 引用实现。它使用跟踪 GC。
  • @EugeneSh。例如,这不适用于循环引用。我正在寻找更多传统的标记和扫描 GC。
  • 我认为您不会得到任何好的答案,因为它完全基于您正在实施的解释器。也许使用现有的并仅修改语法部分会有所帮助。
  • @EugeneSh。你是说每个解释器都实现自己的 GC 吗?我真的很希望有某种便携式库。

标签: c garbage-collection interpreter


【解决方案1】:

在实现这样的语言时,您的解释器需要跟踪它正在运行的程序中的所有对象,包括了解它们的类型以及数据的哪一部分是对其他数据的引用。然后,您可以轻松地遍历所有数据并实现您喜欢的任何类型的垃圾收集器。没有像试图确定 C 实现的“堆”/“堆栈”/等那样的虚假黑客。定位或猜测可能需要一个指针,因为您正在处理您知道其结构的数据。

【讨论】:

  • 显然我可以花时间实现我自己的不太平庸的 GC。我想知道这个问题是否有现有的解决方案。当然,我知道我必须提供一些关于指针位置的信息。
  • 据我所知,在 SO 上询问库建议是题外话,但我不知道有什么可以插入的。
【解决方案2】:

对于 C 程序,有 2 个选项:Boehm GC 替换 malloc(它是一个 保守的 GC,所以可能不完全符合您的要求,但它要么是 或...),或自己写

但是自己编写并不是那么难。执行 mark-sweep 算法。标记的根集将是您的符号表。你需要另一个表或链表来跟踪所有分配的内存,可以是freed。当您扫描分配列表时,free 任何没有标记的内容。

实际编码当然会更复杂,因为你要遍历这两种数据结构,但是算法本身很简单。你可以做到的。


几年前,我发现自己在同一个搜索中,这些是(并且 AFAIK 仍然是)结果。自己编写是非常有益和值得的。

在实践中,随着 Basile 的回答涉及到很多其他问题。

如果垃圾收集器是从调用堆栈的深处调用的(可能是通过需要更多内存的分配例程),那么必须注意其句柄仍保存在 C 函数的局部变量中的任何分配。调用堆栈,而不是保存到它们的符号表或数据库位置。在我的 postscript 解释器中,我使用一个所有分配器都推送到的临时堆栈来处理这个问题。在所有子例程返回后,该堆栈被主循环清除,并且在标记期间它被认为是根集的一部分。在我的 APL 解释器中,我每次都在主循环周围调用 GC。对于小语言的小程序来说,速度问题不如更可怕的内存泄漏那么重要,至少在影响我的圈子里是这样。

【讨论】:

  • 问题是关于精确 GC,而不是保守的。
  • 谢谢@BasileStarynkevitch,我已经添加了一条说明它不完全相同的说明。
  • 顺便说一句,您的答案并不完全正确。我的回答包含指向可能被重用或鼓舞人心的现有软件的指针。
  • 我的目标是说明一个简化但完整的算法。我很喜欢你的回答。我的有什么遗漏吗?
  • 嗯,手动编写的mark&sweep GC与Boehm的GC相比并没有太大的优势。
【解决方案3】:

如果你想要一个精确 GC(不是保守的,像Boehm's GC,它在实践中表现很好)你应该跟踪本地指针(指向GC-ed数据)变量,否则当您确定没有这样的局部变量时,仅使用几乎为空的调用堆栈调用 GC(顺便说一句,GCC 编译器有这样的mark&sweep garbage collector - 带有由一些专门的gengtype C++ 代码生成器生成的标记例程;那GGC 仅在 之间 次传递中调用)。当然,您还应该跟踪全局(包括静态或线程本地)指针(指向 GC 数据)变量。

或者,有一些字节码虚拟机(如OCamlNekoVM 有),然后本地GC-ed 变量是您的字节码VM 的堆栈和/或寄存器中的变量,并且您在特定位置触发GC以及精心挑选的 VM 解释器要点。 (参见 Ocaml GC 的 this explanation)。

您应该阅读有关Garbage Collection 技术的更多信息,请参阅GC handbook

如果您的 GC 是分代复制,您需要实现写屏障(以处理指向新区域的旧数据的突变)。你可以使用我旧的Qish GC(我不再维护它),或者Ravenbrook's MPS,或者编写你自己的分代复制GC(这在理论上并不难,但在实践中调试GC是一场噩梦,所以工作量很大)。

您可能想使用一些宏技巧(例如我的 Qish 所做的)来帮助保留局部变量。请参阅 Ocaml 文档的 Living in harmony with the garbage collector 部分作为示例(或查看 Qish 内部)。

请注意,在手动编写的 C 代码中处理分代复制 GC 并不友好(因为您需要显式保留本地指针,并且因为您需要写屏障来记住何时修改旧值以具有指向新一代)。如果你想这样做,你的C代码应该在A-normal form中(你不能编码x=f(g(y),z);,但你需要编码temp=g(y); x=f(temp,z);并添加temp作为局部变量,假设xy , z 是本地 GC 变量,fg 返回一个 GC 指针)。实际上,生成 C 代码要容易得多。以我的MELT 域特定语言(扩展和自定义GCC)为例。

如果您的语言是真正的多线程语言(多个 mutator 线程并行分配),那么编写 GC 代码就变得非常棘手。这可能需要几个月的工作(而且调试起来可能是一场噩梦)。

实际上,我今天推荐使用 Boehm 的 GC(注意它是多线程友好的)。一个简单的 mark&sweep 手工编码的 GC 可能不会比 Boehm 的 GC 快。而且您将无法(我不推荐)使用 GGC,GCC 内部的垃圾收集器(IMNSHO,它不是很好;多年前这是一个肮脏的 hack 设计)。

顺便说一句,您可能会考虑 自定义 -e.g.使用MELT- GCC 编译器(通过添加一些特定于应用程序的__attribute__#pragma)来帮助您的GC。通过一些工作,您可以生成一些标记例程等。但是,这种方法可能非常痛苦(我真的不知道)。请注意,MELT(免费软件,GPLv3+)包含一个复制的分代 GC,其老年代是 GGC 堆,因此您至少可以查看code of melt-runtime.cc 的内部

PS。我也推荐Queinnec的书:Lisp In Small Pieces;它有一些关于 GC 及其与编程语言的联系的有趣材料,当您实现解释器时,这是一本很棒的书。 Scott 在Programming Languages Pragmatics 上的书也值得一读。

【讨论】:

  • 哇,我不知道 GCC 带有一个 GC(它被称为 GGC...太多的首字母缩略词!)。这似乎正是我正在寻找的那种东西,谢谢!
  • GGC 仅在 GCC 编译器内部。对你来说无所谓(除了作为GC的例子,我不太喜欢,因为我觉得GGC不是一个很好的GC实现)
  • @哦,真令人失望。但是,如果我链接到 GCC 源代码,似乎可以使用它。我想从开源项目中借用现有的实现可能是获得我想要的最好的方法。但也感谢所有其他提示!
  • 查看 Ravenbrook 的 MPS。它可能非常适合您的需求
猜你喜欢
  • 2011-10-29
  • 2013-03-29
  • 1970-01-01
  • 2011-10-15
  • 2011-03-12
  • 2017-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多