Garbage Collector?

Started by
21 comments, last by Kyall 11 years, 6 months ago
Unique pointers are a type of smart pointers.

The topic was about garbage collectors, and typically garbage collectors thread pointers as shared pointers. While unique pointers are also a type of smart pointers, they are significantly different then what garbage collectors do. So I specifically meant shared pointers when I was talking about it being an antipattern, as in most cases there is no such thing as shared ownership.

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.

I'm afraid you misunderstood the situation I was explaining. Those three type of smart pointers you mentioned (unique, shared and weak) are exactly the ones the lack the ability to maintain the class invariant when object B needs to have a reference to object A, but doesn't own it. Neither shared pointer (as B doesn't have ownership, not even shared ownership) or weak pointer (as these can expire, which according to the class invariant is not allowed) make this kind of relationship possible. Of course you can use a weak_ptr in B to A here, and assert if B wants to use the pointer but it's no longer there, but it's a pain in the ass to debug an assertion that triggers any given time after the class invariant was broken. Preferably you assert at the moment this happens, so at the moment A is deleted while there are still non-owning references to this A that cannot exist in a valid state without this reference.

So personally, I would make a difference between ownership (99% of the cases non-transferable and non-shared), weak references (that automatically turn to null if object is destroyed) and hard references (for objects that cannot exist without the object pointed to, but neither own it). This would be a much more powerful system then garbage collectors can offer, with immediate assertion on fatal errors rather then continuing with one or more objects in invalid state for some time until it crashes or starts displaying undefined behaviour. Which from experience I can tell is extremely painful to debug as there is no callstack going back to when the problem really happened if it may even have happened minutes ago.
Advertisement
I shy away from using any kind of GC system in C++ projects, for the same reason that I shy away from all the nifty Boost features: When I need the facilities of a higher level language, I prefer to use a higher level language. ;)

+---------------------------------------------------------------------+

| Game Dev video tutorials -> http://www.youtube.com/goranmilovano | +---------------------------------------------------------------------+
What is the problem you are trying to solve with garbage collection? Are you trying to deal with cross-object references and lifetime management?

Even if you go with garbage collection, you should still avoid run-time allocations and explicitly run the garbage collector at load-time or whenever you can hide the massive frame spike you will get.
-- gekko
Personally I found that if the engine provides a framework in which every game object or objects of a specific system are centrally managed its easier to keep track of allocated memory and avoid leaks.
For instance, I have a render system (abstracted from any other functionality of the engine, it just knows how to render stuff) that you register all your graphic objects to, when the system is requested to shutdown or clear its contents (to change scenes or something like that) it will take the responsibility of deleting any graphic content registered to it.


The biggest advantage of smartpointers in my opinion is that you are guaranteed not to leave invalid pointers around when you do delete the object. If the likelyhood of that is very high for certain objects, smartpointers are a worthwhile solution.
Most of these problems however are avoided by being clean and organized when designing code (something that sometimes and for the peril of the programmers is skipped altogether) and having well defined interfaces to steer the users of a given code towards the right behavior.
Game making is godlike

LinkedIn profile: http://ar.linkedin.com/pub/andres-ricardo-chamarra/2a/28a/272



What is the problem you are trying to solve with garbage collection? Are you trying to deal with cross-object references and lifetime management?

Even if you go with garbage collection, you should still avoid run-time allocations and explicitly run the garbage collector at load-time or whenever you can hide the massive frame spike you will get.


My main interest in GC is related to RTS-type games. I'm experimenting with different ways to reach a good balance between game performance and resource management. The suggestion of smart pointers is definitely interesting and I'm probing that scenario.

In the RTS genre, you constantly have dynamic objects being created and destroyed. The main thing I'm trying to work out is this:

Would a single sweep to collect a large amount of destroyed units be more efficient than interrupting the gameflow in order to destroy each individual unit as it is no longer in the game? Think of multiplayer RTS battles like Supreme Commander or Total Annihilation where you can literally have 100+ units being destroyed within the span of "seconds" (vs "minutes"). Interrupt game logic to destroy each unit individually as it becomes unreferenced or sweep occasionally to destroy several unreferenced units?
For something like a unit in an RTS, there's a definite point where it's no longer required -- when it's been destroyed (by damage, etc).
A GC will actually get in the way of this. For example, let's say upon creating a unit, it's added to a list of "player 1's units". One of player 2's tanks then targets our new unit. The unit then self destructs and is removed from the "player 1's units" list, however, it's not yet garbage, because player 2's tank still contains a "target" reference to it, so it's not freed.

Strong/weak smart-pointers do work though -- if the "player 1's units" list contains strong pointers, but the tank's "target" link is a weak pointer, then when the unit self-destructs and removes itself from the "player 1's units" list, then the tank's "target" will be set to null automatically, and the unit freed.

As for the cost of creating/deleting units - this should be almost completely cost-free. Either creating or destroying an instance of a unit shouldn't contain much code. If you're making a lot of fixed-size allocations, then a pool allocator will work very well, and is often used for things like projectiles or 'monsters' in games that are created/destroyed often.
Mostly In game is object memory pool, or directly allocate and delete by their manager!
I think this way will make game more flexible and efficiency.
I don't use garbage collection. Garbage collections and such cause too many hassles and inefficiencies. I like absolute control of the memory I'm using. Allocation and freeing can get deep into problems with fragmentation, cache issues and more.

One thing I like to do, for example, is reuse already allocated memory when it's not being used. This can increase performance of dynamic objects significantly and if it's using certain methods you can push fragmentation out of your mind forever.

I'd actually suggest smart pointers.


I second that. You can use reference counting to destroy object instances when nothing holds a reference an object any longer. You can also hook up your reference count system so that the count is decremented when a smart-pointer falls out of scope... the result: automatic disposal. If you're up for a challenge and know what you're doing you can even override C++'s new and delete operators so that you can allocate all objects to a custom, managed heap; but you will need to implement heap defragmentation, optimized memory copy/move algorithms and quite a few other things. I can be done but is hard... however, the end-result can be a spectacular and efficient memory management system.

Just to demonstrate what I'm talking about:

[source lang="cpp"]void f()
{
SmartPtr<Model> pCarModel = somePtr;

// do stuff with the car model...

pCarModel->ReleaseRef( );
}[/source]
_______________________________________________________________________________
CEO & Lead Developer at ATCWARE™
"Project X-1"; a 100% managed, platform-agnostic game & simulation engine

Please visit our new forums and help us test them and break the ice!
___________________________________________________________________________________
Edit:
@ATC: Damn! Ninja'd! I should have read the whole thread.

Original:

For handling instances where a unit references another unit, I.E. it's target that it is firing at, I would have a shared_ptr list of the entities in an entity factory or I would have a shared_ptr list of the entities in a factory and then a weak_ptr list of those same entities pointing to the shared_ptr entities in a target aquisition class.

Then get a weak_ptr to the object, so that we can test if it exists or not before we use it in the A.I. procedure of the thing that is targeting it.

This isn't really GC, but it's a safe way of handling inter-entity coupling that will not lead to dead pointers floating around. And I think it's a good example of the use of the weak_ptr.

A shared_ptr isn't necessarily for shared resources, it's what you use to hold a pointer to any dynamically allocated object where the dynamically allocated object exists at least for the lifetime of the object that is owning it.
weak_ptr exists to get rid of pointers that are pointing to deleted data.

And that's about what I know on the topic. I've never had the need to use unique_ptr, it's not bad or anything, it's just a tool to fit a design pattern of preventing more than 1 object from holding onto 1 value that I haven't had the need for as I don't program strict classes like that.



class EntityFactory {

std::weak_ptr<Entity> GetEntity( /* name, position, targeting data */ );

protected:
// Use a map or an oct tree depending upon your situation
std::vector<std::shared_ptr<Entity>> entities;
};

class Tank : public Entity {

void FireAtTarget() {
if( std::shared_ptr<Entity> shared_target = target.lock() ) {
// We now have a reference to target, fire at it
// maybe test if target is dead and then unset target
} else {
// Target has been destroyed, release the reference to it, or find a new target
std::weak_ptr<Entity> novalue;
target = novalue;
}
}
protected:
std::weak_ptr<Entity> target;
};



With shared_ptr and weak_ptr I don't really see the need for GC. And in addition, for things like particle systems you should be using statically allocated memory to handle those so you don't have the alloc/dealloc overhead. Like the following:



class Particle {
public:
// call ParticleAllocator::GetInstance()->CreateParticle() to get the memory for this particle
void* operator new(size_t);
// call ParticleAllocator::GetInstance()->DeleteParticle(p) to free the memory for this particle
void operator delete(void*);
};

class ParticleAllocator {
ParticleAllocator() {
hInstance = this;
for( unsigned int i = 0; i < max_particles; i++ ) {
dead_particles.push_back( &particle_memory );
}
}
void DeleteParticle(Particle* p) {
dead_particles.push_back( p );
}
Particle* CreateParticle() {
vector<Particle*>::iterator it = dead_particles.begin();
if( it != dead_particles.end() ) {
dead_particles.erase(it);
return *it;
} else {
// We have no more particles we can use
return NULL;
}
}

static ParticleAllocator* GetInstance() { return hInstance; }
protected:
static ParticleAllocator* hInstance;

Particle particle_memory[max_particles];
vector<Particle*> dead_particles;
};



This isn't really a great example of a particle system since batch rendering can't really be done with it, you might need to change it up for that but it's a good example of using statically allocated memory for dynamic objects. As a rule of thumb you want to use statically allocated memory for any dynamic object that has a short lifetime. For objects that have longer lifetimes, statically allocating memory doesn't really have much of an advantage since you end up wasting memory to ensure you have enough static allocations to meet you needs and if you're too conservative with your budget of memory for those objects you end up having bugs that arise from not having enough objects to allocate to every so often with one system or another.

As to GC.

In languages that have garbage collection generally they work by every variable in the game being passed by reference, essentially every object is stored as a shared_ptr in C++ 11 terms. And then there is a garbage collector that watches the references and deletes an object when there are no more references to it.

C# Example


// Object with value 5 created
Object myobj = Object(5)
// Object with value 6 created, object value with 5 is no longer referenced
myint = Object(6);

// Some time later: Object with value 5 is destroyed.



C++ Example


// Object with value 5 created
shared_ptr<Object> my_object = shared_ptr<Object>( new Object(5) );
// Object with value 6 created
// We do not need to delete Object(5), because it has it's reference count set to zero in the following code, it is immediately deleted.
my_object = shared_ptr<Object>( new Object(6) );


So what the shared_ptr gives you is the same sort of memory behavior of higher level languages, but much more immediately. Instead of using a GC.
I say Code! You say Build! Code! Build! Code! Build! Can I get a woop-woop? Woop! Woop!

This topic is closed to new replies.

Advertisement