Object Pooling

Hi guys! Last post about scrolling lists was supposed to be the last one from that miniseries, but I decided to present the complete solution, which requires some notes about the object pooling mechanism. That is also a good moment for a little garbage collecting story, so let’s get started!

Identifying a problem

There are many situations when there is a need of spawning and destroying a huge bunch of objects. We could name for example firing bullets, spawning enemies, or – less game-specific – creating large, dynamic lists. (☞ﾟ∀ﾟ)☞ Using Unity’s default mechanisms we could implement such functionality using the Instantiate and Destroy methods. Instantiate creates a new object in memory, and Destroy destroys it. There would be nothing specifically wrong about that, if it wasn’t for the Garbage Collection (GC), which has hands full of work with cleaning all the mess that our bullets leave behind.

GC is a process of automated memory deallocation – thanks to that we don’t have to worry about freeing object’s references by ourselves, thus we are less vulnerable to memory leaks. This is the most common definition (at least for me), but I found a more philosophical one here: Garbage collection is simulating a computer with an infinite amount of memory. Is does so by going through the references graph from the root and checking if these referenced objects reference other ones. If they do, GC marks these blocks of memory as alive (Mark&Sweep algorithm). After the whole lookup, the blocks that are left unmarked are considered free for further allocations. You probably noticed that going through such a huge graph has to have some impact on the CPU usage and you’re right. This is why the memory is allocated in three generations:

• Generation 0 (GEN0) – objects that are recently created, thus there is a good chance that they will be destroyed soon. GC goes through this block most often.

• Generation 1 (GEN1) – when objects are marked as alive several times (the count is dynamically calculated by GC to provide best performance) they probably ale longer lasting ones, so there is no need of checking them s frequently. This is why they advance to the next generation.

• Generation 2 (GEN2) – analogously, memory blocks from GEN1 that are marked as alive several times, advance to this next generation, which is visited by the GC even less often.

Having all of that information leads us to the obvious conclusion – if we create large number of short-lasting elements, we have them all located at GEN0, which means the references graph that is most frequently scanned by the GC is getting bigger. That creates a noticeable impact on the CPU usage.

The real enemy

However, in my case (scrolled list) digging into the profiler’s graph showed that much more significant impact had the operation of instantiating objects. There is a huge pike caused by the GC, but in the constant smaller pikes creating objects took the most of the time. That may be because in Unity, C# objects are just a thin wrappers over the C++ objects, and of course scripts initializing the objects can be costly too.

The solution

Rather than constantly creating and destroying objects, it is better to reuse them through the application lifetime.

On the next episode…

In response to the last post I got a lot of requests of performance comparison, so in the next one I will present a complete example from our Kickerinho World game, both on the Windows and Android platform, so stay tuned!