Garbage Collector?

Started by
21 comments, last by Kyall 11 years, 6 months ago
Is it adviseable to implement a custom garbage collector for a game engine written in C++, or just delete objects immediately as soon as they become unreferenced?

If a GC should be implemented, what criteria / guidelines should be adhered to (in terms of when the GC should be called)?
Advertisement
I'd actually suggest smart pointers. You may be forced into a situation where you have to work with a raw pointer, in which case you should delete it as soon as it is about to go "out of scope" (or whenever makes the most sense). You can also use a bit of a mix between smart pointers and raw pointers if necessary (smart pointers to actually maintain the lifespan of an object, and raw pointers for passing around for functions that only temporarily need that reference).

The main complaint people have with garbage collectors is that they may collect garbage at a bad time and cause a weird occasional stutter in your game. Whether or not that actually happens depends on the garbage collector, how you use it, and how you're using memory.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
The only time that I've seen a "GC" used in a C++ game engine was on a games console where the game had to manage VRAM itself (usually your graphics driver does this), so when deleting a VRAM resource, like a texture, it was put into a queue of resources to be released in 2-frames time (to account for GPU latency).
...and this wasn't really a garbage collector, but a garbage queue.

In a multi-threaded game engine that I wrote, when an object's reference count reached zero, it was also put in a queue for deletion, which would occur after a sync point, otherwise another thread could possibly try and access the object after it had been deleted. Again though, this isn't as much a "GC" as a multi-threaded reference counter.

Usually GC's in games only exist in the embedded extension/'scripting' language, like C#/Lua/Python etc...

  1. Is it adviseable to implement a custom garbage collector for a game engine written in C++, or
  2. just delete objects immediately as soon as they become unreferenced?


  1. I strongly advice to do so for your engine itself. It makes sense to do so with loaded resources using reference counting. A full mark-and-sweep garbage collection would require each collectable object to be able to export pointers to all other objects they are using. In my experience, it was very easy to forget export a pointer and causing a leak. I'm not 100% sure efficient GC is possible at all in C/C++.
  2. Perhaps I'm being pedantic but once an object is unused, it doesn't change much whatever we delete it now or a few seconds later. The point is not to delete objects, but to figure out objects which can be safely deleted.

Previously "Krohm"


  1. ... I'm not 100% sure efficient GC is possible at all in C/C++.



Have you tried Boehm's Garbage Collector (http://www.hpl.hp.com/personal/Hans_Boehm/gc/)? In my experience, it's quite fast.

Perhaps I'm being pedantic but once an object is unused, it doesn't change much whatever we delete it now or a few seconds later. The point is not to delete objects, but to figure out objects which can be safely deleted.


That depends on the object. For instance, an object might lock a mutex in its constructor and unlock it in its destructor. You wouldn't want that object destroyed a few seconds after it should be. Of course in that case it will probably live on the stack and this is not an issue. But my point is that destructors might do more than just clean up memory. I don't like garbage collectors for this reason.
I second smart pointers. Smart pointers are modern C++'s "garbage collector", but are not enabled by default, and you just enable them explicitly for the variables you want. C++'s unofficial motto is: "Only pay for what you use", smart pointers are you-only-pay-for-when-you-want-it "garbage collection".
It really depends on the nature of the object. For resources as meshes and textures, I have some custom object management system that makes sure these objects are released if they are unreferenced for a few seconds. For objects like this it makes sense, as there isn't a clear ownership. However, for objects that do have a clear ownership, smart pointers or garbage collection doesn't really make sense, and could even be considered anti-pattern. They can easily break the class invariant, if there is a composition relationship between class A en B where class A owns zero or more instances of class B, deleting A should delete all instances of B that were linked to this A. The garbage collector keeping instances of B alive because of some remaining references while it's owner is destroyed can result in your application misbehaving as it's in a state you didn't consider possible in the design.

Unique pointers on the other hand are something different, and these are more suitable for situations like this. They ensure there is a clear ownership, and if the owner is deleted the owned objects are as well. There is still a risk of breaking the class invariant using unique pointers however, if you have an object C that has a 1..1 aggregation relationship to class B, and B gets deleted, C will become invalid as it is missing it's required link to B. But at least now you can assert on that situation. Personally I would introduce a third kind of smart pointer, consisting of a unique pointer (owner), weak pointer (reference that may change to null if object is deleted) and hard pointer (reference that will assert if object is deleted). That way your class invariant can be kept valid through the automatic pointer management. Shared pointers should be used only for situations where there really is a shared ownership of the pointer, which is almost never the case.

Have you tried Boehm's Garbage Collector (http://www.hpl.hp.co.../Hans_Boehm/gc/)? In my experience, it's quite fast.
Whatever it's fast in practice is a different thing than being efficient.What I understand from the Boehm collector is that it scans objects blobs for pointers. We can see some additional machinery in the last C++ revision, which adds support for GC and allows to specify memory areas as "guaranteed to not contain pointers".
This is a key point.
Languages such as C# or Java forbid modification of memory which is not associated to a type: they have intrinsic knowledge of what it's being allocated. C/C++ has not and this causes the GC to run much more conservatively.
Rather than bolting GC on C++, I'd consider another language.


That depends on the object. For instance, an object might lock a mutex in its constructor and unlock it in its destructor.
Yes correct. Perhaps we should debate if this is correct usage. I think it's not. And this is the main reason in my opinion to not use GC. People tends to forget that object lifetimes are important.

Here's another very nice example I've seen in the past.
A game used particle systems to make explosions. They were created on the fly each time it's needed. Rather than disabling the PS after a certain time, this code relied on the finalizer to get the PS cleaned. What it happened is that the explosion sometimes "looped". Rather than fixing the problem, the author made an habit of calling GC collect every time possible and this still didn't work 100% of the cases.

While destructors can be trusted for a variety of operations, I think they lose that property when they become finalizers. If timely release is a concern, either GC goes out of the window, or we call [font=courier new,courier,monospace]finalize [/font]manually.

Previously "Krohm"

However, for objects that do have a clear ownership, smart pointers or garbage collection doesn't really make sense, and could even be considered anti-pattern. They can easily break the class invariant, if there is a composition relationship between class A en B where class A owns zero or more instances of class B, deleting A should delete all instances of B that were linked to this A.

Which is what smart pointers do. They give clear ownership to objects that otherwise wouldn't have clear ownership, and delete the object the moment it is no longer being referenced (not seconds later - the exact moment it is no longer referenced).

The garbage collector keeping instances of B alive because of some remaining references while it's owner is destroyed can result in your application misbehaving as it's in a state you didn't consider possible in the design.[/quote]
Garbage collection, yes, proper usage of smart pointers, no.

Unique pointers on the other hand are something different, and these are more suitable for situations like this.[/quote]
Unique pointers are a type of smart pointers.

They ensure there is a clear ownership, and if the owner is deleted the owned objects are as well. There is still a risk of breaking the class invariant using unique pointers however, if you have an object C that has a 1..1 aggregation relationship to class B, and B gets deleted, C will become invalid as it is missing it's required link to B.[/quote]
That's exactly what smart pointers solve. Look up weak_ptr.

But at least now you can assert on that situation. Personally I would introduce a third kind of smart pointer, consisting of a unique pointer (owner), weak pointer (reference that may change to null if object is deleted) and hard pointer (reference that will assert if object is deleted).[/quote]

I just went over this in a different thread, so I'm just going to copy+paste the relevant portion here:
std::unique_ptr = I own this object, nobody else owns it _or_ uses it.
std::shared_ptr = I own this object, possibly with shared ownership (through other std::shared_ptrs), and possibly with shared use (through weak_ptrs).
std::weak_ptr = I don't own this object, I just use it.
Regular raw pointer '*' = I don't own this object, I just use it (only use if the pointer's lifetime is garunteed to be longer than the class containing it)

Smart pointers solve most the things you are complaining about. They declare the intent of ownership and usage, explicitly state the lifetime of the object, delete the object instantly (not seconds later), with far less risk than manual management, and keep things compact in terms of memory usage, without large performance hits.
There is still place for resource manager classes, and sometimes in a performance critical area, you still need to use raw pointers. But I certainly don't think smart pointers are an anti-pattern, unless misused.

If you want assertions instead of exceptions, that's something you can easily do on your own: assert(!myWeakPtr.expired())

This topic is closed to new replies.

Advertisement