Pool Manager, Delegates and Garbage Collector

I`ve been reading about optimization in Unity, basically trying to avoid as much as possible feeding GC so I have two questions in mind:

1st.- Pool Manager and GC = About pooling, I’ve seen there’s always behind the scenes some Queue or Stack to keep your references to your stocked objects. So when you add or remove elements from any of those structures, aren’t you allocating/deallocating memory and as such feeding the GC?

2nd.- Delegates and GC = Does suscribe/unsubscribe to delegates feed the GC?

Related to my second question it has to do with the fact that I’m learning by making an arkanoid/brick breaker clone. So I have 13x18 = 234 bricks at most. For example, let’s say I have some special power that turns my ball unstoppable so one touch is enough to any brick to say goodbye and I need my ball to pass through the bricks not to collide with them so I have to turn every brick collider into a trigger. I’ve read that BroadcastMessage and SendMessage should be avoided thinking of GC and CPU performance so I came to delegates as an alternative messaging system. I’dont want my bricks to check every update cycle a timer and check/uncheck the collider as a trigger or different timers according to different powers.

I know that for a project like this might not be necessary to take into account all this stuff but learning it from the beginning will make it easier in harder future projects.

I would appreciate any help or suggestions. Sorry for my still learning english and Thanks in advance!

  1. Depends on the container class you are using. Go for something that supports setting the capacity of the container (eg. List) then set the capacity on startup to ensure all the storage you need is allocated up front.

  2. When you assign a function to a delegate reference it will implicitly create an object derived from MulticastDelegate.

// Delegate type
delegate void MyDelegate();

// Function
void MyFunction() { ... }

// Delegate reference
MyDelegate myDelegate;

// When you assign MyFunction to myDelegate you get an implicit allocation
myDelegate = MyFunction;

To avoid heap churn make sure this only happens once. To do this, cache a reference to the delegate at startup and then use this to assign to other references rather than directly using the function.

MyDelegate otherDelegate = myDelegate; // Assigns MyFunction to otherDelegate without allocating

@Gibbonator, thank you for the answer.

So, for the pool manager you are thinking about a pre-sized structure and instead of adding/removing may be cycling through the elements until finding one available and if not then creating a new one, right?

And for the delegates, i don’t get it. I was thinking about the ‘+=’ , ‘-=’ systax. So when a brick is first created, let’s say in the ‘Start’ method I suscribe to the delegate invocation list using ‘+=’ sysntax and when the brick is deactivated using the ‘-=’ syntax to exclude the brick’s method from the list?

Thanks again for your fast reply!

You shouldn’t need to worry too much about GC. Implement IDisposable on your objects if you want a Dispose method but in general, objects that are marked as Disposed will be removed by the GC in time. The .NET GC dynamically deletes stale objects when it needs to and not before.

You can do a GC.Collect(); to force garbage collection but this adds an overhead and will probably lag your game. Leave the GC to it.

Adding handlers to events is just a pointer and does not require GC.

  1. For the pool manager you don’t need to do anything special other than set the capacity. It will be up to the container class itself to perform any memory management, shuffling of elements etc. behind the scenes.

  2. Ah ok, that’s something slightly different. I’m not 100% sure how it works but you normally get an allocation doing this. The way I think it works is the + and - operator for MulticastDelegate takes two delegates (or null) and combines them into a new third delegate that references the new set of functions, it then returns the new delegate.

To get around this problem in my own projects I created a generic class that behaves a bit like an event. It supports the +=, -= syntax but behind the scenes it manages it’s own list of delegates rather than combining them in the normal way. I then used this in all the places I would normally have delegates or events.

@cgarossi

IDisposable is used for releasing unmanaged resources. It doesn’t provide any hints to the GC, if an object is still referenced it won’t be collected.

Pointers need space to be stored, the more pointers you have in your list the more space you need. They all go on the managed heap.

Edit: Should probably explain the pointers a bit better. The delegate reference isn’t a direct reference to the function you want to call, it’s a reference to a delegate object (normally MulticastDelegate). It’s up to this object to store pointers to the actual functions that get called. You get allocations on the managed heap by creating these objects (normally implicitly).

It marks the object as ready to be collected though if you are using unmanaged objects inside a managed wrapper. It’s simply states the object is now invalid.

Since this is all managed code you don’t need to worry about GC anyway as long as you are destroying any references to objects when they aren’t needed.

If you remove an object from your pool manager, the total stack will reduce in accordance to the size of the objects within it. GC takes care of this.

@Gibbonator and @cgarossi, again thank you for the answers.

@Gibbonator, I’ll point the development of my pool manager in the direction you suggested and for the delegates system looks like every time you add a new method to the delegate you are allocating memory and every time you remove a method (‘-=’) you are deallocating memory so seems there’s no way to avoid feeding the GC using delegates.

Perhaps you could post some pseudocode with your solution so that I can understand it better? Or do you have in mind a better solution about what I’m trying to accomplish what’s basically a way no notify multiple elements at the same time of some occurrence in the game, ie. hey brick (to every brick still in game) now you’ll be a trigger because the unstoppable ball is playing!

Thanks for your patience and anyone that can add suggestions or correct me is welcome. Seriously, is a great learning process for me.-

The idea behind the delegate stuff would be to replace the built in delegate combining with your own list. I wrapped this up in a class but you may not need to do this if your delegate code is only in one place.

// Before: You have a reference to a delegate
MyDelegate myDelegate;

// After: You have a list of delegate references
List<MyDelegate> myDelegateList = new List<MyDelegate>();

 // Before: Combine/Remove delegates
myDelegate += MyFunction;
myDelegate -= MyFunction;

// After: Add/Remove cached delegate references from the list
myDelegateList.Add(cachedDelegateOfMyFunction);
myDelegateList.Remove(cachedDelegateOfMyFunction);

// Before: Call delegate
myDelegate();

// After: Call all delegates in list
for (int i = 0; i < myDelegateList.Count; ++i)
{
  myDelegateList[i]();
}
2 Likes

You’re completely missing the point of a pool manager.

1 Like

@Gibbonator, understood. Excellent explanation and thanks a lot for your help!