Memory usage concerns with Object pooling

Hello there,
I am working on an open-world game, where I divided my world into multiple small chunks that I load and unload depending on the player’s distance. When loading a chunk, I load all the objects in that chunk one by one. And it could be anywhere from 50 to 500 objects. Because there are so many objects when the load action is triggered there is a big spike that is associated with GC allocation.

I am thinking of using an object pooling system, so that when I load a chunk I get an object from a pool or if non-available I create a new one, and when unloading, I return all the objects from that chunk back to a pool.

I am sure this would solve the garbage collection problem, however, my concern is memory usage.
In my game, there are biomes, where in one biome there could be completely unique objects that could not be found in other biomes.
So, if I am across the entire map, far away from some specific biome, all the biome objects would still be loaded, just deactivated, which would still occupy memory.

If I instantiate and destroy every time I load/unload a chunk, I have GC allocation problems, but all the non-needed objects would also not occupy memory.
Cause one of the reasons I stream the entire world with chunks, is to avoid large memory usage.

Also, I cannot use scenes, because chunk objects can change at runtime, meaning the chunk content is not fixed, so I have to store all data in a file.

I am curious what is the right approach when dealing with this problem. Would appreciate the help

Seems like a proper engineering problem with tradeoffs. You can’t magically have memory exist without it taking space or being allocated/deallocated. I don’t know if there’s a single solution here, and I doubt one exists. Here are a couple ideas:

  1. What exactly is the requirement you’re aiming at? How many objects can you allocate before your performance suffers?

  2. For unique vs common objects, it seems like it would be possible to create a PoolMe component you could attach to any prefabs to tell your pooling system to pool it. Alternatively, you could just decide to pool all prefabs. You don’t have access to PrefabUtility at runtime, but you can create a MonoBehaviour that stores this information to be used at runtime.

  3. Have you thought about using ECS instead of GameObjects? There will still be a spike, but it should be faster than the GC to allocate. Have you tested loading chunks via ECS instead of GameObjects?

  4. Have you tried using a combination of scenes for the static parts of the chunk objects, and game objects for the dynamic parts? Maybe you could load the static part, then play all the changes using the changes in the file?

Did you profile to see where the GC alloc originates from? Some or all of this may be in your own code, such as string concat “one” + “two” will cause GC garbage. Make sure you profile and optimize your own code for minimal garbage creation.

Because honestly, I don’t think 500 objects should create significant enough garbage that you’ll notice a dip in performance because of it. I do streaming game objects when rotating and moving the camera along tile bounds, so that happens very frequently for me and with hundreds of instantiated game objects. However I’m also in the editor, not playing, so it may just feel smooth to me and my strategy is probably different (I’m in the 2nd to 3rd rewrite too).

As to pooling you can pool a generic game object or prefab, but it just displays a different mesh. You can use that strategy to avoid instantiate/destroy cycle and rather replace the MeshFilter/Renderer. Although I cannot say if this will be more efficient, it’s something you have to test.
You can also go for clever pooling strategies but honestly, if your target platform is PC then just check how many you can keep in memory. Even if that cache consumes 1 GB memory with the rest of the game using 2 GB you’re still very much in the green for machines with 8 GB of memory.

Lastly, Unity has an Incremental Garbage Collector. It’s disabled by default (except for some older Unity versions), look for it in the Player settings. It should help distribute the garbage collection time over several frames.

You have to test it and find approach that works the best through testing.

Pooling reduces delay by increasing memory usage. So it is a classic situation where you can get better performance by increasing memory usage.

Also, do keep in mind that modern hardware is quite powerful.

You need numbers, and you need a target hardware spec. Without those things you can’t make useful decisions.

E.g. if you want your game to run on a certain phone, then start by finding out how much memory an app can have on it, and do some testing to see how much pooling changes your memory usage and performance.

Any time you get to the point where you are considering using an object pool, you should also consider tricks to reduce the number of game objects. For example, can you replace hundreds of game objects with GPU instancing (DrawMeshInstanced)? Also, do you have game objects that could be merged together using tools like MeshBaker?

1 Like

Generally when designing an open-world with streaming you need to carefully think about your architecture.
First you should consider your target platforms (i.e. is it a console with a spinning disk, will the users have an ssd or will it be a slow mobile phone?).
Based on this you will have to implement a specific streaming system - in terms of AAA games you can look at GTA as it has one of the most robust streaming systems (that probably took serious time to implement).
You can see that in such games certain objects such as street lamps, cars, etc are repeated in every part of the world to reduce the memory footprint (but they are still object pooled).

Unfortunately unity is using a language with a garbage collector (and a pretty bad one due to the use of Mono) - thus implementing a good streaming solution in Unity will be an even harder task.

Generally depending on your goal you might want to look for engines that support large-world streaming out of the box (such as Unreal)

1 Like