Pooling and instantiation methods

Mine is an open-world game, I don’t have machine gun bullets, but I do have a lot of monsters and objects that come on and off as the player moves around and terrains are generated / destroyed. I’ve never used pooling because I’ve never really had a problem with anything and I’ve a ways until I start optimizing for version 1.0. Having said that I started looking into pooling after a conversation about it started and I’ve found some interesting results.

This is using an animated SkinnedMeshRenderer .
I’m pulling from the pool, or instantiating, 50 at a time and repositioning them randomly until 5,000 objects have been used. So that’s 5,000 Instantiate() and Destroy() or the repeated reuse of a pool of 50 GameObjects. I do the 5,000 loop for 10 iterations and then average the time it took across all ten iterations to create a final average time, all within a single frame:

Pool using GameObject.SetActive true/false: 2.244811 (310x)
Instantiating and Destroying with forced GC.Collect per iteration: 0.8234555 (114x)
Instantiating and Destroying with no GC.Collect: 0.7385982 (102x)
Pool using Component.enabled true/false: 0.007248688 (1x)
Pool with no enable/disable: 0.002649307 (0.37x) (I consider this impractical)

If you believe what your average random Unity blog or YouTube video tells you they generally say to SetActive(false) and return objects to the pool, but according to this test that’s actually slower than simply instantiating and destroying.

Of course the “real” benefit of pooling is supposed to be the reduction of garbage generated and then picked up by the GarbageCollector, which i know very little about, so I may not be seeing the real results of GC in runtime, but the performance increase generated by component caching instead of using SetActive is 310x. Given there are only 2 components in this test, and I certainly don’t instantiate 5,000 objects in a single frame, but that’s a big difference.

I’ve attached the script in case I’m doing something incorrect.

I’m imagining a component directly beneath the Transform with references to all further components that should be turned on/off when leaving or returning to the pool would reduce realtime uses to a single GetComponent during runtime instantiation and storing each type of prefab in dictionaries with enumerator keys and GameObject[ ] pools, sized to fit different probable uses, like 10, 50, 100.

If anyone could enlighten me further on this subject I would appreciate it. Or offer any further pointers I’d appreciate it.

5113976–504869–Pooltest.cs (4.93 KB)

1 Like

Here is an output in a standalone build:

Total GameObjects: 1000. Per cycle: 50. Iterations: 10
Pool using GameObject.SetActive true/false: 0.1211197 (124.954x)
Pool using GameObject.SetActive true: 0.1036415 (106.9225x)
Instantiating and Destroying with forced GC.Collect per iteration: 0.07480888 (77.17709x)
Instantiating and Destroying with no GC.Collect: 0.07294197 (75.25108x)
Pool using Component.enabled true/false: 0.0009693146 (1x)
Pool with no enable/disable: 0.0002227783 (0.2298308x)

I also added a test where the SetActive(false) time taken was subtracted from the iterations, so it’s only a pull from the pool, not a return and a pull.

5114234–504923–PoolTest.cs (9.74 KB)

Interesting test, unexpected results. Don’t see anything wrong in the cs. Have you tried it with other components on the GOs? This might be specific to the SkinnedMeshRenderer. Luckily all my pooling is already just the components but this might be something unity could optimize under the hood.

I use the component enable/disable style. I disable the collider and on the rigidbody disable gravity and set velocity to 0, then place it 50km up in the air where it can’t be seen. Glad to see that should perform better than the competition. :slight_smile: Good info. I know that they have improved instantiate/destroy performance over the years, but that’s better than I expected.

Is this being tested in the editor or in a build?

I posted numbers from both the editor and in a build.

@ibbybn The SkinnedMeshRenderer should be disabled/enabled with the SetActive() method as well. Not sure why there is such a big difference when turning it off directly versus going through SetActive().

Ah, my apologies, I didn’t see your second post.

1 Like

I’ve just done the same test with the 2020.1.0a7 and for me SetActive is actually faster?

5000/50/10 your default settings
Empty Prefab:
Pool using GameObject.SetActive true/false: 0,007372689
Instantiating and Destroying with forced GC.Collect per iteration: 0,02752473
Instantiating and Destroying with no GC.Collect: 0,02381053
Pool with no enable/disable: 0,0008570671

With SkinnedMesh:
Pool using GameObject.SetActive true/false: 0,009611225
Instantiating and Destroying with forced GC.Collect per iteration: 0,04193797
Instantiating and Destroying with no GC.Collect: 0,03715076
Pool using Component.enabled true/false: 0,002669621
Pool with no enable/disable: 0,0008450508

Sounds like premature optimisation. How many things are being instantiated per frame, sustained? if it’s not many then you will just be ruining your code for absolutely no reason. Unity even has a garbage collector that divides the work over several frames now so it’s kind of utterly pointless to be doing this unless you’re spawning bullets or something.

Just a bit of perspective.

Also did profiler show this as a performance problem?

No, I’m not incorporating this into anything and agree the numbers don’t support a complete rework of anything, I don’t have any profiler problems with Instantiate/Destroy to begin with, it’s just something people bring up all the time I thought I’d try it out for myself.

I’ve been running the test in 2018.3.9f1 by the way.

Also, DOTS is basically immune from the GC. So if you really cared about performance, you would want that. You can instantiate 100,000 things in a couple of millisecs - no GC.

I’m more interested in trying 2020 than anything right now =o
Upgrading a live project is always so dangerous though =o=