Language agnostic 垃圾收集和运行时类型信息

Language agnostic 垃圾收集和运行时类型信息,language-agnostic,garbage-collection,language-implementation,Language Agnostic,Garbage Collection,Language Implementation,让我想到另一个我想了很久的问题 许多关于垃圾收集的在线资料都没有说明如何实现运行时类型信息。因此,我知道很多关于各种垃圾收集器的知识,但不知道如何实现它们 fixnum解决方案实际上相当不错,很清楚哪个值是指针,哪个不是指针。还有哪些其他常用的存储类型信息的解决方案 还有,我想知道关于fixnum的事情。这难道不意味着在每个数组索引上都只能使用fixnums吗?或者是否有某种方法可以获得完整的64位整数?基本上,为了实现准确的标记,您需要元数据来指示哪些字用作指针,哪些字不用作指针 这个元数据可

让我想到另一个我想了很久的问题

许多关于垃圾收集的在线资料都没有说明如何实现运行时类型信息。因此,我知道很多关于各种垃圾收集器的知识,但不知道如何实现它们

fixnum解决方案实际上相当不错,很清楚哪个值是指针,哪个不是指针。还有哪些其他常用的存储类型信息的解决方案


还有,我想知道关于fixnum的事情。这难道不意味着在每个数组索引上都只能使用fixnums吗?或者是否有某种方法可以获得完整的64位整数?

基本上,为了实现准确的标记,您需要元数据来指示哪些字用作指针,哪些字不用作指针

这个元数据可以像emacs那样按引用存储。如果对于您的语言/实现,您不太关心内存的使用,您甚至可以使引用比单词大一倍,这样每个引用都可以携带类型信息以及它的一个单词数据。这样,您就可以拥有一个32位指针大小的fixnum,而所有引用的代价都是64位

或者,元数据可以与其他类型信息一起存储。因此,例如,一个类和通常的函数指针表一样,可以包含数据布局中每个字的一位,指示该字是否包含垃圾收集器应该遵循的引用。如果您的语言有虚拟调用,那么您必须已经有了从对象计算出要使用的函数地址的方法,因此相同的机制将允许您计算出要使用的标记数据-通常在每个对象的开头添加一个额外的秘密指针,指向构成其运行时类型的类。显然,对于某些动态语言,所指向的类型数据需要在写入时进行复制,因为它是可修改的

堆栈也可以执行类似的操作-将准确的标记信息存储在代码本身的数据段中,并让垃圾收集器检查存储的程序计数器和/或堆栈上的链接指针,和/或代码为此而放置在堆栈上的其他信息,确定堆栈的每一位与哪些代码相关,从而确定哪些字是指针。轻量级异常机制倾向于做类似的事情来存储关于try/catch在代码中发生的位置的信息,当然调试器也需要能够解释堆栈,因此这很可能与实现任何语言所做的其他工作结合在一起,包括内置垃圾收集功能的

请注意,垃圾收集不一定需要精确的标记。您可以将每个单词视为指针,不管它是否真的是指针,在垃圾收集器的所有内容的大列表中查找它,以确定它是否可能引用尚未标记的对象,如果是,则将其视为对该对象的引用。这很简单,但成本当然是介于相当慢和非常慢之间,这取决于gc用于查找的数据结构。此外,有时一个整数恰好与一个未引用对象的地址具有相同的值,并导致您保留一大堆本应收集的对象。因此,这样的垃圾收集器不能为收集未引用的对象提供强有力的保证。这对于玩具实现或第一个工作版本来说可能很好,但不太可能受到用户的欢迎


比如说,一种混合的方法可能会对对象进行精确的标记,但不会对堆栈中的某些区域进行标记,因为在这些区域中,事情会变得特别复杂。例如,如果您编写一个JIT,该JIT可以创建一个代码,其中引用的对象地址只出现在寄存器中,而不出现在通常的堆栈插槽中,那么您可能需要不准确地跟踪堆栈区域,操作系统在重新调度相关线程以运行垃圾回收器时存储了寄存器。这可能相当棘手,因此可能导致代码速度变慢的合理方法是要求JIT始终在准确标记的堆栈上保留其使用的所有指针值的副本。

基本上,要实现准确标记,需要元数据指示哪些字用作指针,哪些字不用作指针

这个元数据可以像emacs那样按引用存储。如果对于您的语言/实现,您不太关心内存的使用,您甚至可以使引用比单词大一倍,这样每个引用都可以携带类型信息以及它的一个单词数据。这样,您就可以拥有一个32位指针大小的fixnum,而所有引用的代价都是64位

或者,元数据可以与其他类型信息一起存储。所以 示例一个类可以包含,以及通常的函数指针表,数据布局的每个字一位,指示该字是否包含垃圾收集器应该跟随的引用。如果您的语言有虚拟调用,那么您必须已经有了从对象计算出要使用的函数地址的方法,因此相同的机制将允许您计算出要使用的标记数据-通常在每个对象的开头添加一个额外的秘密指针,指向构成其运行时类型的类。显然,对于某些动态语言,所指向的类型数据需要在写入时进行复制,因为它是可修改的

堆栈也可以执行类似的操作-将准确的标记信息存储在代码本身的数据段中,并让垃圾收集器检查存储的程序计数器和/或堆栈上的链接指针,和/或代码为此而放置在堆栈上的其他信息,确定堆栈的每一位与哪些代码相关,从而确定哪些字是指针。轻量级异常机制倾向于做类似的事情来存储关于try/catch在代码中发生的位置的信息,当然调试器也需要能够解释堆栈,因此这很可能与实现任何语言所做的其他工作结合在一起,包括内置垃圾收集功能的

请注意,垃圾收集不一定需要精确的标记。您可以将每个单词视为指针,不管它是否真的是指针,在垃圾收集器的所有内容的大列表中查找它,以确定它是否可能引用尚未标记的对象,如果是,则将其视为对该对象的引用。这很简单,但成本当然是介于相当慢和非常慢之间,这取决于gc用于查找的数据结构。此外,有时一个整数恰好与一个未引用对象的地址具有相同的值,并导致您保留一大堆本应收集的对象。因此,这样的垃圾收集器不能为收集未引用的对象提供强有力的保证。这对于玩具实现或第一个工作版本来说可能很好,但不太可能受到用户的欢迎


比如说,一种混合的方法可能会对对象进行精确的标记,但不会对堆栈中的某些区域进行标记,因为在这些区域中,事情会变得特别复杂。例如,如果您编写一个JIT,该JIT可以创建一个代码,其中引用的对象地址只出现在寄存器中,而不出现在通常的堆栈插槽中,那么您可能需要不准确地跟踪堆栈区域,操作系统在重新调度相关线程以运行垃圾回收器时存储了寄存器。这可能相当棘手,因此可能导致代码速度变慢的合理方法是要求JIT始终保留其在精确标记堆栈上使用的所有指针值的副本。

在Squeak Alway Scheme和许多其他动态语言中,我猜您有SmallInteger,有符号的31位整数类,和用于任意大整数的类,例如大正整数。很可能还有其他的表示法,64位的整数,或者是作为完整的对象,或者是带有一些位,因为我不是指针


但是算术方法被编码来处理过流/欠流,例如,如果向SmallInteger maxVal添加一个,则得到2^30+1作为LargePositiveInteger的实例,如果从中减去一个,则得到2^30作为SmallInteger。

在Squeak Alway Scheme和许多其他动态语言中,我猜您有SmallInteger,有符号31位整数的类,以及任意大整数的类,例如大正整数。很可能还有其他的表示法,64位的整数,或者是作为完整的对象,或者是带有一些位,因为我不是指针


但是算术方法被编码来处理过流/欠流,例如,如果向SmallInteger maxVal添加一个,则得到2^30+1作为LargePositiveInteger的实例,如果从中减去一个,您将返回2^30作为一个小整数。

我确信您可以使用BigNumbers作为数组索引。我确信您可以使用BigNumbers作为数组索引。我知道C有一个保守的垃圾收集器,它没有运行时类型信息。我想我现在更了解它了,因此我接受这个答案。+1作为元数据的替代方案,你可以让每个对象包含一个指向其类型的指针,它的类型可以包含一个指向一个函数的指针,该函数可以标记从该类型的任何值直接引用的所有内容。我在HLVM中使用了这种方法,效果很好。这些访问函数是在看到新的类型定义和JIT编译时动态生成的,只要有可能预先确定将为子级调用哪个访问函数,它们就可以直接相互引用,从而为您提供近似的操作
timal标记没有任何元数据的性能@乔恩:同意,我称之为元数据的信息可以体现为代码。希望我上面写的内容在考虑到这种可能性的情况下仍然有意义。C有一个我知道的保守的垃圾收集器,它没有运行时类型信息。我想我现在更了解它了,因此我接受这个答案。+1作为元数据的替代方案,你可以让每个对象包含一个指向其类型的指针,它的类型可以包含一个指向一个函数的指针,该函数可以标记从该类型的任何值直接引用的所有内容。我在HLVM中使用了这种方法,效果很好。这些访问函数是在看到新的类型定义和JIT编译时动态生成的,只要有可能预先确定将为子级调用哪个访问函数,它们就可以直接相互引用,从而在没有任何元数据的情况下为您提供接近最佳的标记性能@乔恩:同意,我称之为元数据的信息可以体现为代码。希望我上面写的东西在考虑到这种可能性的情况下仍然有意义。