Mastering C# and Unity3D

Using Object Pooling to Reduce Garbage Collection

Unity’s garbage collector is old and slow. As garbage from your scripts piles up, the garbage collector will eventually run. When it does, it runs all at once instead of spreading the work out over multiple frames. This means you get a big spike on one frame causing your game to momentarily freeze. One of the best ways to get around this is to use an “object pool” to reduce the number of objects eligible for garbage collection. After all, if there’s no garbage then there’s nothing to collect! For more on this strategy as well as a class you can use to implement it, read on!

The general idea of the “object pooling” strategy is to prevent garbage from ever being created so the garbage collector has nothing to collect and therefore no need to run. Normally, when you’ve released all your references to a class instance then it will become garbage. When enough garbage piles up, the garbage collector will run and usually cause a hitch in your game’s frame rate.

To accomplish this, we use an “object pool” for two purposes. First, when an object is no longer needed the object pool can keep holding a reference to it so that it won’t be considered garbage. Second, the object pool allows for the references it holds to be reused to prevent the need to allocate more memory. Together, this means that overall memory usage stays low while no garbage is ever created.

To implement an object pool, all we really need is a list of objects. A primitive pool would therefore look something like this:

// Make an empty poolvar pool =new Stack<Person>();// Make a Person directlyvar person =new Person("Fred", "Flintstone");
person.SayHello();// Done with the Person, put it in the object pool
pool.Push(person);// Need a new Person, grab one from the pool
person = pool.Pop();// Use the Person from the pool
person.First="Barney";
person.Last="Rubble";
person.SayHello();

This approach has several problems. First, the pool isn’t exactly safe or easy to use. If we try to Pop a person off to use and the Stack is empty, we’ll get an exception. If we put in code to check that the pool isn’t empty, we’ve cluttered up our code.

Let’s address these issues by making the pool into a class:

publicclass ObjectPool
{private Stack<Person> objects;public ObjectPool(){// Initially nothing in the pool
objects =new Stack<Person>();}public Person Get(){// If empty, make a new Person// Otherwise, get one from the poolreturn objects.Count==0?new Person(): objects.Pop();}publicvoid Release(Person person){// Add to the pool
objects.Push(person);}}// Make an empty poolvar pool =new ObjectPool();// Get from the pool (creates a new Person)var person = pool.Get();
person.First="Fred";
person.Last="Flintstone";
person.SayHello();// Done, release back into the pool
pool.Release(person);// Get from the pool (reuses the last Person)
person = pool.Get();
person.First="Barney";
person.Last="Rubble";
person.SayHello();

This is a lot better, but still has issues. The ObjectPool can only hold Person objects. We can fix that using generics like so:

publicclass ObjectPool<TObject>where TObject :class, new(){private Stack<TObject> objects;public ObjectPool(){// Initially nothing in the pool
objects =new Stack<TObject>();}public TObject Get(){// If empty, make a new object// Otherwise, get one from the poolreturn objects.Count==0?new TObject(): objects.Pop();}publicvoid Release(TObject person){// Add to the pool
objects.Push(person);}}// Make an empty poolvar pool =new ObjectPool<Person>();// ... usage is the same

This is way better, but still not quite where it needs to be. When we switched from directly using the new operator to using ObjectPool.Get, we lost our ability to pass instantiation parameters to the Person class. So let’s add that ability to ObjectPool by allowing for an initialization structure to be passed in to Get. We don’t need to worry about the structure itself being garbage since struct is allocated on the stack and automatically released like other local variables without going through the garbage collector. Here’s how ObjectPool looks now:

This step made ObjectPool a lot safer to use by ensuring that it’s always passed the parameters it needs when being initialized. It’s much harder to accidentally forget to set one of its fields—First, Last—when getting one from the pool.

Fianlly, let’s allow for pooled objects to clean themselves up. Since we’re reusing instances, we should make sure to return to a default state when released by the user. To do this, we simply add a Release function to the IPoolableObject interface yielding the final version of the object pool code. You’re free to use it in your projects according to the MIT license.

////////////////////////////////////////////////////// Object pooling system by Jackson Dunstan// Article: http://JacksonDunstan.com/articles/3245// License: MIT////////////////////////////////////////////////////usingSystem.Collections.Generic;/// <summary>/// An object that can be put in a <see cref="ObjectPool{TObject,TInitArgs}"/> /// </summary>publicinterface IPoolableObject<TInitArgs>where TInitArgs :struct{/// <summary>/// Initialize the object/// </summary>/// <param name="initArgs">Arguments to initialize with</param>void Init(TInitArgs initArgs);/// <summary>/// Release the object/// </summary>void Release();}/// <summary>/// A pool of objects to make reusing them easier/// </summary>publicclass ObjectPool<TObject, TInitArgs>where TObject :class, IPoolableObject<TInitArgs>, new()where TInitArgs :struct{/// <summary>/// Unused objects eligible for reuse/// </summary>private Stack<TObject> unused;/// <summary>/// Make the pool with no objects/// </summary>public ObjectPool(){
unused =new Stack<TObject>();}/// <summary>/// Get an object. If there are unused objects from <see cref="Release"/>, one of those will be/// reused. Otherwise, a new one will be instantiated. The object's/// <see cref="IPoolableObject.Init"/> will be called in either case./// </summary>/// <param name="initArgs">Init arguments.</param>public TObject Get(TInitArgs initArgs){var obj = unused.Count==0?new TObject(): unused.Pop();
obj.Init(initArgs);return obj;}/// <summary>/// Release the object so it can be reused later by calling <see cref="Get"/>. A reference is/// kept to prevent the garbage collector from collecting it. Its/// <see cref="IPoolableObject.Release"/> is also called./// </summary>/// <param name="obj">The object to release</param>publicvoid Release(TObject obj){
obj.Release();
unused.Push(obj);}}

And here’s how you use it:

usingUnityEngine;publicstruct PersonInitArgs
{publicstring First {get;privateset;}publicstring Last {get;privateset;}publicint Age {get;privateset;}public PersonInitArgs(string first, string last, int age){
First = first;
Last = last;
Age = age;}}publicclass Person : IPoolableObject<PersonInitArgs>{privatestring first;privatestring last;privateint age;publicvoid Init(PersonInitArgs initArgs){
first = initArgs.First;
last = initArgs.Last;
age = initArgs.Age;}publicvoid Release(){
first =null;
last =null;
age =0;}publicvoid SayHello(){
Debug.Log("Hello, my name is "+ first +" "+ last +" and I'm "+ age +" years old");}}publicclass TestScript : MonoBehaviour
{void Awake(){// Make the poolvar personPool =new ObjectPool<Person, PersonInitArgs>();// Get a person from the pool (allocates)var actor = personPool.Get(new PersonInitArgs("William", "Shatner", 84));
actor.SayHello();// Release a person back into the pool
personPool.Release(actor);// You shouldn't use the person anymore, but this tests that it was cleared by its Release()
actor.SayHello();// Get a person from the pool (reuses)
actor = personPool.Get(new PersonInitArgs("Chris", "Pine", 35));
actor.SayHello();// Get another person from the pool (allocates)
actor = personPool.Get(new PersonInitArgs("Zachary", "Quinto", 38));
actor.SayHello();}}

There are, of course, some downsides to this approach. First and most obviously there is additional complexity over and above simply using the new operator and letting the garbage collector kick in. Second, this approach still has some code “safety” issues. As noted in the example, you can still use objects you’ve sent to Release. That could cause exceptions to be thrown or unexpected behavior to occur. There’s also the need to provide a default constructor. Since you won’t have the init arguments at this point, you’ll have to create an object that isn’t really usable. It’s definitely a violation is the “resource acquisition is initialization” idiom and yet-another price to pay with this approach.

Overall, if you’re willing to live with these downsides then an object pool can be a very effective way to stop the garbage collector from causing framerate hiccups in your game. Please feel free to take the above code, enhance it, modify it to your tastes, and use it in your game. Please share what you’ve done with it in the comments if you do!

Do you use an object pool? How does yours compare to mine? If you don’t, how do you handle garbage collection in your games? Share your thoughts in the comments!

The two extra functions I had in mind to enhance this object pool class were related to just this. First, a Clear function to clear out the unused objects. Second, a maximum size of the unused object pool. That should put a cap on the memory growth, a warning when there are two many, and a mechanism to easily release all the references when you’re ready for a GC run.

Hi,
Thanks for the article. :)
I am not a C# programmer. But I think event-listeners or similar is used in every programming language. And they too, add up to the garbage. In fact they are one of the major concerns in Actionscript.
In the current example, I don’t see event-listeners being pooled. So, would it be advisable to re-use them too, by object-pooling strategy ?

The explicit Init() function in a language that supports CTORs always felt clunky, but since C# doesn’t have in place construction (like C++) it will have to do. Perhaps there is a way to fake this in C# but from a quick Google, it doesn’t look like anyone has found a “clean” technique (e.g., not having to emit IL ops codes).

I too don’t like the Init function. As mentioned in the article, I don’t like the forced default constructor either. I just don’t know a way around this in “clean” C#. Let me know if you come up with something!

This is great, I would add some debug functionalities to the pool, such as a
– max number in the pool (and if the number is reached in the stack, do not push back to the pool or throw an exception)
– some debug code to catch if a “release” is done more than once.
– in the functions in the Person class, maybe add some Assert to verify that the instance currently used has not been released.

I mentioned the first one—a maximum number of objects—in a comment above (after you wrote your comment) as being one of the first additions I wanted to make.

The second one—checking for double-releases—could be done in ObjectPool by keeping track of all objects that have been released and consulting it before releasing. There’s extra CPU cost to pay, but it’s useful functionality. Perhaps some #if preprocessor code could be used to only run it in debug modes.

The third suggestion—verifying that the object isn’t released—reminds me a lot of using IDisposable objects. It’s common to keep a bool isDisposed. The Dispose function sets it to true and every function begins by checking it and throwing an ObjectDisposedException if it’s set. Something similar could definitely be applied in classes implementing IPoolableObject. Unfortunately, I don’t see a way to do it automatically in the object pool “system”, so it’s likely to be a manual process for each pooled object type.

Hi Jackson,
Great article! I have a few questions..
First, as far as I know, stack is index based, and therefore adding/removing elements is not that efficient, right?
And second – isn’t it a good practice to use the “who created it – he’ll dispose it” principle? As letting objects self-release sounds like a nightmare to me :)

Stack uses an array internally, just like List. Calling Push just assigns to an index of that array, which is really fast. If the array is full, a new and longer one is allocated and the existing one is copied to that first. The Pop function just sets an index of the array to null. In short, Stack is nearly as fast as you can get and has a nice, clean interface that matches well with the needs of ObjectPool.

As for who should dispose of instances, that’s a really tricky problem. Often it’s impractical for the creator to be the disposer. In those cases you need to hand off the responsibility to dispose the instance to someone else. Making that a clearly-understood transition is the really tricky part. You don’t want both parties to assume that the other is responsible for disposing. If you do, no one will dispose it! You also don’t want them both to dispose it, as that’s often an error.

In the case of the ObjectPool, the creator is the pool and the disposer is whoever doesn’t call Release on the pool but lets the object get garbage collected. Or if you think of Release as disposing, then it’s no different from any other situation involving an IDisposable like FileStream. You need to come up with some way to clearly indicate who is responsible for calling Dispose or Release on the instance you want to get rid of.

Self-release could be warranted in some situations, but usually you don’t want the objects to know about the pool they should Release themselves back into. Self-cleanup, on the other hand, is explicitly supported via IPoolableObject.Release because there’s nobody better to cleanup the internals of the instance than the class itself. Perhaps the function should be called Cleanup or something, but that’s just a matter of naming.

Hope that helps clear things up. Let me know if you’ve got any more questions and thanks for commenting! :)