March 30 2021
While developing your game, you noticed that instantiating 100 bullets per second is suffocating your mobile CPU performance. You then…
In this blog post, we are going to cover this last option.
Today, you will learn how to use the new Pooling API introduced in 2021.
Since Unity 2021, you have access to a wealthy set of pooling features that will help you develop high-performing Unity projects.
Ready to get to know them?
Let’s start with the most important question: when do you need pooling?
I ask that because pooling shouldn’t be your go-to solution 24/7.
Object pooling in Unity has definitely some important drawbacks that do more harm than good, so you have to be careful there.
We will look into those later.
To keep it short, consider pooling when:
These operations cause lots of allocations and therefore:
Do you feel like those problems can pose a threat for you?
(If not now, they might later)
Let’s keep going.
Now that you know if you are in trouble (or still safe), let me quickly explain what pooling is.
Pooling is a performance optimization technique that is all about reusing C# entities instead of creating and destroying them every time you need them.
An entity can be anything: a game object, an instanced prefab, a C# dictionary, etc..
Let me put the concept of pooling in the context of a real life example.
Let’s say you have to go grocery shopping tomorrow morning.
What do you usually take with you apart from your wallet and keys?
Well, you might take reusable bags. After all, you need some sort of container to bring your rations back home.
So you take your empty reusable bags, fill them with groceries and come back home.
Once you’re home, you empty your bags and put them back into your drawer.
So that’s pool.
Reusable bags are a better alternative than buying (allocating) plastic bags and trashing (deallocating) them every time you go shopping.
You need a bag?
Ok, you go to your pool of bags (e.g. a drawer in the kitchen), you take a few, you use them, you empty them and finally you return them back to the pool.
See what we did there?
Here are the main components of a pooling use case:
In the case of a shooter game, you can create and destroy bullets every single time… or you can create a bunch beforehand and then reuse them like this:
This way, you save the CPU cycles it takes to instantiate and destroy these prefabs. Plus, you alleviate pressure on the garbage collector.
Now, before you jump right into pooling, be aware of a few points…
The pooling technique has a few (potential) issues:
All you need to do is to avoid pooling in cases where you would not really profit from it.
Like, there’s no need to pool the final boss. It’s just one, after all.
Remember: what matters most is the frequency of your instantiate and destroy operations.
If you do them often, consider pooling. Otherwise, skip it.
We will see more pooling issues in detail later on.
Now, let’s have a look at your options for pooling.
If you want to pool your objects in your Unity project, you have three options:
Let’s check them out.
One option is to put your crafting skills into practice.
Implementing your own pooling system doesn’t sound too complex, as you only need to implement a few operations:
But it often gets more complicated than that when you start thinking of:
Does that sound like a headache already?
I can already see your face a bit paler than before…
My suggestion is to not reinvent the wheel (unless it’s a learning exercise).
Since it’s an already-solved problem, use something that works so you can focus on your project.
Focus on bringing the fun to your players.
That’s what they’ll pay you for anyway.
Let’s check the second option.
Here, you choose a 3rd party provider from sources like:
Let’s see a few examples:
But before you click the buy button… keep reading.
3rd party tools can work wonders and have tons of features.
But that comes with drawbacks:
You probably knew all of that, but it’s always a neat reminder :-)
And nowadays there are even fewer reasons to go for a 3rd party asset, since Unity has quietly released a new pooling API in Unity 2021.
And that’s the main topic of this post.
With version 2021 onwards, Unity released a few C# pooling mechanisms that will help you in a myriad of use cases.
These pools of objects are directly integrated in the Unity engine. No extra downloads required and kept up to date on each Unity update.
As a huge plus, you have access to their source code.
And I must say that the implementations are quite straight forward. It’s a nice evening read.
Let’s see how you can start using the Unity Pooling API today so that you can reduce the performance cost of these operations you and I know about.
The first step is to make sure you are on Unity 2021+.
(I mean, you can just copy & paste the code into any of your older projects… but hey, I never said this)
Then, it’s just a matter of knowing the Unity Pooling API:
The first operation you need to do is to construct the pooling container of your choice.
That’s usually done in a single line of code, so don’t worry here.
The constructor parameters depend on the specific container you want to use, but they are quite similar
Here are the usual Unity pooling constructor parameters:
createFunc | Called to create a new instance of your object, e.g. () => new GameObject(“Bullet”) or () => new Vector3(0,0,0) |
actionOnGet | Called when you take an instance the pool, e.g. to activate the game object |
actionOnRelease | Called when you return an instance to the pool, e.g. to clean up and deactivate the instance. |
actionOnDestroy | Called when the pool destroys this item, i.e. when it doesn’t fit (exceeds maximum size) or the pool is destroyed |
collectionCheck | True if you want Unity to check that this item was not already in the pool when you try to return it (only in editor) |
defaultCapacity | Default pool size: initial size of the stack/list that will contain your elements |
maxSize | Pool size: maximum number of free items that are in the pool at any given time. If you return an item to a pool that is full, this item will be destroyed instead. |
Here’s how you can create an object pool of GameObjects:
_pool = new ObjectPool<GameObject>(createFunc: () => new GameObject("PooledObject"), actionOnGet: (obj) => obj.SetActive(true), actionOnRelease: (obj) => obj.SetActive(false), actionOnDestroy: (obj) => Destroy(obj), collectionChecks: false, defaultCapacity: 10, maxPoolSize: 10);
I left the parameter names for clarity; feel free to skip them in production code :-).
And sure enough, this is just an example with a GameObject. You can use it with any type you want.
Okay, now you have a pool of _GameObject_s.
How do you use it?
The first thing Unity needs to know how to create more of your _GameObject_s whenever you request more than are available.
We already specified that in the constructor, as we passed the createFunc function as the first parameter to the pool constructor.
Every time that you want to take a GameObject from an empty pool, Unity will create one for you and give it to you.
And to create it, it will use the createFunc function you passed.
And how do we take a GameObject from the pool?
Now that you have your pool reference stored in _pool, you can call its Get function:
GameObject myGameObject = _pool.Get();
That’s it.
Now you can use the object as you wish (within certain limits).
When you are done with it, you need to return it back to your pool so you can use it later on.
So you have been using your element for a few minutes and now you’re done with it.
What now?
Here is what you do not do now: you do not destroy/dispose it yourself.
Instead, you return it to the pool so that the pool can manage its lifecycle correctly according to the functions you provided.
How do you do that? Easy:
_pool.Return(myObject);
The pool will then:
That’s it.
And talking about destroying elements…
Whenever you dispose your pool or there is no internal space to store the elements you return, the pool will destroy that element.
And it does so by calling the actionOnDestroy function you passed in its constructor.
This function can be as simple as doing nothing… or calling Destroy(myObject) if we are talking about objects managed by Unity.
And finally, whenever you are done with your pool, you should dispose it.
Disposing your pool is all about freeing up the resources owned by the pool.
There is often a stack or a list inside of your pool that contains the elements that are free to take.
Well, you dispose your pool by calling:
_pool.Dispose();
Okay, so that’s the functionality there is to pooling.
But we are still missing one important bit…
Not all pools are created for the same use case.
Let’s see what pool types Unity offers to cover your needs.
The first group of pools are those that cover generic C# objects (95%+ of the elements you might want to pool).
A typical use case for these type of pools are game objects — whether instantiated from prefabs or not —.
The difference between LinkedPool and ObjectPool is the internal data structure that Unity uses to hold the elements you want to pool.
An ObjectPool simply uses a C# Stack, which uses an C# array underneath:
private T[] _array;
Being a stack, it contains a big chunk of contiguous memory.
Its worst case is having 0 items (length = 0) in a big pool (capacity = 100000). There, you’ll have a big chunk of reserved memory that you’re not using.
Resizing stacks happens when you go over its capacity. And that’s expensive, as you need to allocate a bigger chunk and copy your elements over.
Hint: you can avoid stack resizing by playing with the maxCapacity constructor parameter.
LinkedPool uses a linked list, which may lead to better memory management depending on your case. Here’s how the data structure looks like:
internal class LinkedPoolItem
{
internal LinkedPool<T>.LinkedPoolItem poolNext;
internal T value;
}
With LinkedPool, you only use memory for the elements that are actually stored in the pool.
But that comes with an extra cost: you spend more memory per item and more CPU cycles to manage this data structure.
You probably know the differences between arrays and linked lists, anyway.
So let’s talk about the next category of Unity object pooling classes.
Now we are talking about pooling C# collections in Unity.
You see, in game development you most likely need to use lists, dictionaries, hashsets and collections alike.
And often enough, you need to create/destroy these collections frequently.
We do this often in AI when running specific one-off behaviors or algorithms. There, we frequently need supporting data structures to perform searches, evaluations and scoring.
So that’s the thing…
Whenever you create and destroy collections, you put pressure onto the memory management system. That’s because you:
So a solution that helps with some of these run-time allocations in Unity is collection pooling.
Whenever you need a list, you can just grab it from a pool, use it and return it when done.
Here’s an example:
var manuallyReleasedPooledList = ListPool<Vector2>.Get();
manuallyReleasedPooledList.Add(Random.insideUnitCircle);
// Use your pool
// ...
ListPool<Vector2>.Release(manuallyReleasedPooledList);
And here’s a different construct that releases the collection pool for you:
using (var pooledObject = ListPool<Vector2>.Get(out List<Vector2> automaticallyReleasedPooledList))
{
automaticallyReleasedPooledList.Add(Random.insideUnitCircle);
// Use your pool
// ...
}
Whenever you go out of the scope of that using block, Unity will return the list to the pool for you.
CollectionPool is the base class for these specific collections; so if you make your own collections you can create a pool for them by inheriting from that one.
ListPool, DictionaryPool and HashSetPool are specific pools for their respective collection types.
Now, you have to be careful with these collection pools.
I say that, because internally, all of these collection pools from Unity work based on a static pool variable.
Which means…
Using these collection pools will break a feature that improve your iteration times in the editor: disabling domain reload.
If you use static pools like these carelessly, the pooled elements will persist across runs in the editor.
And that’s no fun.
Lastly, let’s have a look at the other evils: GenericPool and its twin UnsafeGenericPool.
They are, like their names describe, generic object pools. But there’s something special about them…
So, what’s the deal with these pools of objects?
Again, both GenericPool and UnsafeGenericPool are static object pools. So using them won’t let you disable domain reloads to reduce your editor iteration times.
On the bright side, you don’t have to bother to create them for any of your use cases.
You just use them, whenever, wherever (and whoever) you are.
var pooledGameObject = GenericPool<GameObject>.Get();
pooledGameObject.transform.position = Vector3.one;
GenericPool<GameObject>.Release(pooledGameObject);
Just like that.
The UnsafeGenericPool variant performs better at the cost of skipping an important check: the object already-returned check.
You see, when you return an object to the pool, it might be that you already returned it in the past (and didn’t take it out of the pool).
That can be the case easier than you think, especially if you use the static pools and you use the same objects in multiple places
In that case, an item might appear twice in the pool’s internal data structure.
And guess what happens when you take two elements?
BANG!
You’ll be using the same object in different places. You’ll be overwriting each other’s changes.
Imagine you used the same character game object for two different players.
To sum up the differences:
Ok, so as you saw not everything about pooling is nice and neat.
Let’s rub more salt into the wound.
I could write at least 3 blog posts detailing nasty issues you might experience with pooling.
But instead of doing that, I’ll sum them up here based on an excellent post by Jackson Dunstan.
Here are some of the issues you might face with pooling:
Some good food for thought there.
Pools are excellent tools to reduce:
And since Unity 2021+ it is now easier than ever to adopt pooling as a developer lifestyle, since we now have a built-in pooling API.
However, I explained the dark side of pooling. A side that might bring you tons of pain during the development of your project.
Pooling is just another performance tool you must know. And the more tools you know, the better.
Do you want to know more tools?
And not only tools, but workflows, optimization tactics and high-performance secrets?
The thing is, you don’t have time to learn the 20% that brings you the 80% your project needs.
That’s why you shouldn’t be on your own in the first place.
Because I know this well, I created the Unity Performance Taskforce. An exclusive membership for high-performers where you will get in-depth content every week, every month on the Four Game Performance Pillars:
The taskforce includes live lessons where you can ask your questions on the fly.
And before you ask: yes, you'll also join our exclusive Discord community.
If you liked this post, I extend you a risk-free invitation to try the Unity Performance Taskforce to stay up-to-date in the industry of high-performing games.