简体   繁体   English

在C ++ 0x中压缩垃圾收集器实现

[英]Compacting garbage collector implementation in C++0x

I'm implementing a compacting garbage collector for my own personal use in C++0x, and I've got a question. 我正在C ++ 0x中实现一个压缩垃圾收集器供我个人使用,我有一个问题。 Obviously the mechanics of the collector depend upon moving objects, and I've been wondering how to implement this in terms of the smart pointer types that point to it. 显然,收集器的机制取决于移动对象,我一直想知道如何根据指向它的智能指针类型来实现它。 I've been thinking about either pointer-to-pointer in the pointer type itself, or, the collector maintains a list of pointers that point to each object so that they can be modified, removing the need for a double de-ref when accessing the pointer but adding some extra overhead during collection and additional memory overhead. 我一直在考虑指针类型本身的指针指针,或者,收集器维护一个指向每个对象的指针列表,以便可以修改它们,从而在访问时无需双重de-ref指针但在收集过程中增加了一些额外的开销和额外的内存开销。 What's the best way to go here? 什么是最好的方式去这里?

Edit: My primary concern is for speedy allocation and access. 编辑:我主要关心的是快速分配和访问。 I'm not concerned with particularly efficient collections or other maintenance, because that's not really what the GC is intended for. 我并不关心特别有效的收藏或其他维护,因为这不是GC的目的。

There's nothing straight forward about grafting on extra GC to C++, let alone a compacting algorithm. 关于将额外的GC移植到C ++,没有什么是直接的,更不用说压缩算法了。 It isn't clear exactly what you're trying to do and how it will interact with the rest of the C++ code. 目前尚不清楚你正在尝试做什么以及它将如何与其余的C ++代码进行交互。

I have actually written a gc in C++ which works with existing C++ code, and it had a compactor at one stage (though I dropped it because it was too slow). 我实际上用C ++编写了一个gc,它可以使用现有的C ++代码,并且在一个阶段有一个压缩器(虽然我放弃它因为它太慢了)。 But there are many nasty semantic problems. 但是有许多令人讨厌的语义问题。 I mentioned to Bjarne only a few weeks ago that C++ lacks the operator required to do it properly and the situation is that it is unlikely to ever exist because it has limited utility.. 几周前我才向Bjarne提到C ++缺乏正确执行操作所需的操作符,而且情况是它不太可能存在,因为它的实用性有限。

What you actually need is a "re-addres-me" operator. 你真正需要的是一个“re-addres-me”运算符。 What happens is that you do not actually move objects around. 发生的事情是你实际上没有移动物体。 You just use mmap to change the object address. 您只需使用mmap更改对象地址。 This is much faster, and, in effect, it is using the VM features to provide handles. 这要快得多,实际上,它使用VM功能来提供句柄。

Without this facility you have to have a way to perform an overlapping move of an object, which you cannot do in C++ efficiently: you'd have to move to a temporary first. 如果没有这个功能,你必须有一种方法来执行一个对象的重叠移动,这是你无法有效地在C ++中完成的:你必须首先移动到临时。 In C, it is much easier, you can use memmove . 在C中,它更容易,你可以使用memmove At some stage all the pointers to or into the moved objects have to be adjusted. 在某个阶段,必须调整所有指向或移动对象的指针。

Using handles does not solve this problem, it just reduces the problem from arbitrary sized objects to constant sized ones: these are easier to manage in an array, but the same problem exists: you have to manage the storage. 使用手柄解决这个问题,它只是减少了从任意大小的天体恒定尺寸的,这个问题:这些都是比较容易在一个阵列来管理,但存在同样的问题:你要管理存储。 If you remove lots of handle from the array randomly .. you still have a problem with fragmentation. 如果从数组中随机删除大量句柄,则仍然存在碎片问题。

So don't bother with handles, they don't work. 所以不要打扰句柄,它们不起作用。

This is what I did in Felix: you call new(shape, collector) T(args) . 这就是我在Felix中所做的:你称之为new(shape, collector) T(args) Here the shape is a descriptor of the type, including a list of offsets which contain (GC) pointers, and the address of a routine to finalise the object (by default, it calls the destructor). 这里的shape是类型的描述符,包括包含(GC)指针的偏移列表,以及完成对象的例程的地址(默认情况下,它调用析构函数)。

It also contains a flag saying if the object can be moved with memmove . 它还包含一个标志,说明是否可以使用memmove移动对象。 If the object is big or immobile, it is allocated by malloc . 如果对象很大或不可移动,则由malloc分配。 If the object is small and mobile, it is allocated in an arena, provided there is space in the arena. 如果对象很小且移动,则在竞技场中分配,如果竞技场中有空间。

The arena is compacted by moving all the objects in it, and using the shape information to globally adjust all the pointers to or into these objects. 通过移动其中的所有对象来压缩竞技场,并使用形状信息全局调整指向这些对象的所有指针。 Compaction can be done incrementally. 压缩可以逐步完成。

The downside for a C++ programmer is the need to construct a correct shape object to pass. C ++程序员的缺点是需要构造一个正确的shape对象来传递。 This doesn't bother me because I'm implementing a language which can generate the shape information automatically. 这并没有打扰我,因为我正在实现一种可以自动生成形状信息的语言。

Now: the key point is: to do compaction, you must use a precise collector. 现在:关键点是:要进行压缩, 必须使用精确的收集器。 Compaction cannot work with a conservative collector. 压缩无法与保守的收集器一起使用。 This is very important. 这个非常重要。 It is fine to allow some leakage if you see an value that looks like a pointer but happens to be an integer: some object won't be collected, but this is usually no big deal. 如果你看到一个看起来像指针但恰好是整数的值,允许一些泄漏是很好的:一些对象不会被收集,但这通常没什么大不了的。 But for compaction you have to adjust the pointers but you'd better not change that integer: so you have to know for sure when something is a pointer, so your collector has to be precise: the shape must be known. 但是对于压缩,你必须调整指针,但你最好不要改变那个整数:所以你必须确定什么东西是指针,所以你的收集器必须精确:形状必须是已知的。

In Ocaml this is relatively simple: everything is either a pointer or integer and the low bit is used at run time to tell. 在Ocaml中,这是相对简单的:一切都是指针或整数,并且在运行时使用低位来表示。 Objects pointed at have a code telling the type, and there are only a few types: either a scalar (don't scan it) or an aggregate (scan it, it only contains integers or pointers). 指向的对象有一个告诉类型的代码,并且只有几种类型:标量(不扫描它)或聚合(扫描它,它只包含整数或指针)。

This is a pretty straight-forward question so here's a straight-forward answer: 这是一个非常直截了当的问题,所以这是一个直截了当的答案:

Mark-and-sweep (and occasionally mark-and-compact to avoid heap fragmentation) is the fastest when it comes to allocation and access (avoiding double de-refs). Mark-and-sweep (偶尔mark-and-compact以避免堆碎片)在分配和访问(避免双重删除)方面是最快的。 It's also very easy to implement. 它也很容易实现。 Since you're not worried about collection performance impact (mark-and-sweep tends to freeze up the process in a nondeterministically), this should be the way to go. 由于您并不担心收集性能影响(标记和扫描倾向于以不确定的方式冻结过程),因此应该采用这种方式。

Implementation details found at: 实施细节见:

A nursery generation will give you the best possible allocation performance because it is just a pointer bump. 托儿所生成将为您提供最佳的分配性能,因为它只是一个指针凹凸。

You could implement pointer updates without using double indirection by using techniques like a shadow stack but this will be slow and very error prone if you're writing this C++ code by hand. 您可以通过使用像阴影堆栈这样的技术来实现指针更新而不使用双重间接,但如果您手动编写此C ++代码,这将很慢并且非常容易出错。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM