Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

My experiments with object pooling versus instantiate/destroy.

Discussion in 'Scripting' started by casperjeff, Jul 23, 2014.

  1. casperjeff

    casperjeff

    Joined:
    Apr 13, 2013
    Posts:
    333
    One of the rules I’ve lived by as a progressional developer was to “optimize last”.

    I’ve followed that rule pretty closely as I near completion of a side-scrolling 3d space shooter.

    Working fairly blind (without Unity Pro - and hence no profiler), I am working through my ‘optimization’ list - one item of which is object pooling.


    Most of my enemy/terrain objects are already pooled by virtue of an asset I am using - but I generate a huge amount of ‘other’ game objects in the form of missiles, lasers, and most of all , explosions. Explosions mostly consists of particle systems - occasional components added to them to de-parent parts when appropriate for cool post-explosion effects.


    I decided to start implementing an object pool provided by Jean Moreno as part of his Cartoon FX assets. It’s very well written package - and I’ve used it in the past without issue (it uses a dictionary to look up prefabs by instance id).

    about 75% done with my ‘conversion’ away from instantiate/destroy, I ran into a couple of snags that would require me to further refactor some things - and before I put a lot of energy into it, I though I would give myself a wam and fuzzy with a performance test before and after.

    I set up a test whereby I would randomly instantiate explosion prefabs - using both the object pool and the instantiate/destroy method.. My performance metric would be based on the Update() timing I was able to measure - accumulating average information and noting ‘stutters’ (which I defined as any update call that was greater than 1/30 of a second from the last one) My whole goal was to reduce/eliminate any ‘stutters’ caused by GC or other nuances of the engine dealing with lots of instantiation and destruction of game objects.

    Running on my mac, my results were mostly expected - very little (if any) stutter with either method - and no discernible difference between the two.

    Without object pooling
    ———————————————

    Run time: 300 seconds (5 minutes)
    Objects created: 2833
    Average Update() differential (time between calls to Update): .0169
    Stutters counted: 24

    With object pooling
    —————————————————————

    Run time: 300 seconds (5 minutes)
    Objects created: 2796
    Average Update() differential (time between calls to Update): .0168
    Stutters counted: 23

    I then moved the test to my android Galaxy S4


    Without object pooling
    ———————————————

    Run time: 300 seconds (5 minutes)
    Objects created: 2760
    Average Update() differential (time between calls to Update):.0530
    Stutters counted: 4907

    With object pooling
    —————————————————————

    Run time: 300 seconds (5 minutes)
    Objects created: 2763
    Average Update() differential (time between calls to Update): .0541
    Stutters counted: 4860

    I’m debating ripping out the work I’ve done thus far tonight and migrating these explosions BACK to simple instantiate/destroy techniques.

    I guess I could run the tests longer…….but I'm tired now. :)



    Any comments on my experiment (technique or results)?
     

    Attached Files:

    perevezentsev likes this.
  2. cowtrix

    cowtrix

    Joined:
    Oct 23, 2012
    Posts:
    322
    Object pooling is vastly better than Instantiate/Destroy patterns, in my experience. I'm very surprised that your results don't reflect that and would say that your test is flawed. Instantiate and Destroy are literally two of the most expensive calls you can make in Unity.
     
  3. casperjeff

    casperjeff

    Joined:
    Apr 13, 2013
    Posts:
    333

    :)
    Is there a way you might advise me to 'adjust' my test to bear that out?
     
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    The gains you get from object pooling aren't necessarily because retrieving from a pool is so much faster than create/destroy - although it probably is at least marginally faster; it's that you avoid the garbage collection involved in cleaning up the objects after you destroy them.

    Looking at your test - what you're probably encountering is poor performance from the reflection involved in using Invoke. Also - if you're not requesting things from the pool fast enough then you're probably just instantiating new things all the time anyway (depending on the lifetime of your objects). Without a profiler or knowing how the pool is implemented it's going to be a tough road figuring out what's actually happening.
     
  5. casperjeff

    casperjeff

    Joined:
    Apr 13, 2013
    Posts:
    333
    Agreed - that's why my real metric was the 'stutter' count - the number of update() calls that were over 1/30 of a second.

    The pool I am using pre-allocates the objects to start with (in this case 100 per - which is plenty for the speed I am requesting them at) - I dont start timing anything until after that pre-allocation.

    To summarize what my results show -
    Essentially no measurable difference in 'stutter' between instantiate/destroy versus pooling (in this limited case)
     
  6. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Without the profiler there is no way to guarantee that your "stutter" is coming from the pool at all. Given that it "stutters" roughly the same with both methods leads me to believe it's not that in fact.
     
  7. casperjeff

    casperjeff

    Joined:
    Apr 13, 2013
    Posts:
    333
    My contention is that the 'stutter' (when it occurs) is simply the particle systems on mobile....
    :)
    Being that the results of instantiate versus pool are nearly IDENTICAL, I have no reason to believe there is any benefit for one method over the other.
     
  8. Jean-Moreno

    Jean-Moreno

    Joined:
    Jul 23, 2012
    Posts:
    590
    So I didn't understand exactly how you ran your tests:
    Do you spawn 1 effect only every N seconds?
    Did you see the stutters each time you spawned an effect? (either Instantiate or pooled)
    How many objects did you preload in the pool?

    What I understood from Unity is that when you calling Instantiate on a prefab for the first time, you will have a relatively long stutter (it depends on your CPU of course, but also on the complexity/size of the prefab) because I guess the prefab first has to be loaded into memory, and then each subsequent time you Instantiate it you will have another stutter which is the time required to copy the object into a new one.

    Pooling has 2 benefits to me:
    - it Instantiates all objects on the first frame, so that you only have a stutter when starting your Scene (possibly while loading other things), and then the system only activates/deactivates GameObject depending on the game's demands, and it should be way faster than having to create and destroy GameObjects all the time (but it might not be discernible when running your tests on very fast CPUs)
    - you keep control of the memory used by your GameObjects, so you know that a certain pool of effects will only take up to the preloaded amount defined (times the size of the prefab); that is if the pooling system isn't meant to Instantiate new objects when it needs it, as it is the case with the Cartoon FX Spawn System

    My suggestion if you want better results is to rerun your tests but with a lot more effects, and to spawn them by batches of 10 effects for example (or even 100). Make sure that you preload exactly the amound spawned, and wait for the effects to be available again before spawning a new batch. I think it should give you a better idea of the differences between pooling and instantiation.
     
  9. Nullxero

    Nullxero

    Joined:
    May 10, 2015
    Posts:
    2
    You know how many times I have seen this debate, where someone creates a test to show that pooling does nothing and someone else chimes in "Obviously you are stupid, pooling is great" -- but they provide no experiment, no proof. I can't find any proof anywhere to suggest that it is 'vastly superior'. I ran my own tests and came to the same conclusions (in tests) that pooling performance boosts are marginal at best. This MAY NOT BE TRUE, if you have objects with complex cpu intensive startup sequences as the scripts will need to run every single time they are created, but I would expect objects need to be 'reinitialized' in a pool and startup scripts run anyway even if they are coming from a pool. Prove me wrong.
     
  10. skauth

    skauth

    Joined:
    Jun 4, 2017
    Posts:
    17
    I still think that it's possible that pooling is vastly superior - if done "right".
    But given that no one can tell those who find pooling equivalent what they're doing wrong, or how to do it in such a way to actually decrease stutters, then the pooling that such people have access to does not count as the vastly superior pooling. And superior techniques that you don't have access to might as well not exist.
     
    BarriaKarl and vargad like this.