Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Pool API not working properly and not improving perfomance.

Discussion in 'Scripting' started by theokiwi, Nov 21, 2023.

  1. theokiwi

    theokiwi

    Joined:
    Jul 2, 2020
    Posts:
    4
    Hello! I'm trying to learn how to use the Unity Pool API and I'm having some trouble, also i was not sure of which topic to use on the forum so i did a quick research and found some pool related questions in the scripting so i guess it goes here. My code (below) doesn't give any error warning but it gives the exactly same perfomance as if i was just spawning objects normally, also it never runs the destroy function no matter how much i change the pool size parameters.


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Pool;
    5.  
    6. public class SpawnPool : MonoBehaviour
    7. {
    8.     public ChestPool chestPrefab; // Prefab of the object to be pooled
    9.     public int spawnNum;  
    10.     public float spawnInterval;
    11.     public float spawnStart;
    12.  
    13.     public int defaultCapacity;
    14.     public int maxPoolSize;
    15.  
    16.     public static SpawnPool Instance;
    17.     public ObjectPool<ChestPool> _pool;
    18.  
    19.     void Start()
    20.     {
    21.         Instance = this;
    22.  
    23.         _pool = new ObjectPool<ChestPool>(createFunc,actionOnGet,actionOnRelease, actionOnDestroy, true, defaultCapacity, maxPoolSize);
    24.  
    25.         InvokeRepeating("ObjectSpawn", spawnStart, spawnInterval);
    26.     }
    27.  
    28.     void ObjectSpawn()
    29.     {
    30.         for (int i = 0; i < spawnNum; i++)
    31.         {
    32.             ChestPool chestObj = _pool.Get();
    33.         }
    34.     }
    35.  
    36.     ChestPool createFunc()
    37.     {
    38.         ChestPool chestObj = Instantiate(chestPrefab, Random.insideUnitCircle * 3, Quaternion.identity);
    39.         chestObj.SetPool(_pool);
    40.  
    41.  
    42.         return chestObj;
    43.     }
    44.  
    45.     void actionOnGet(ChestPool chestObj)
    46.     {
    47.         chestObj.transform.position = Random.insideUnitCircle * 3;
    48.         chestObj.transform.rotation = Quaternion.identity;
    49.  
    50.          chestObj.gameObject.SetActive(true);
    51.  
    52.     }
    53.  
    54.     void actionOnRelease(ChestPool chestObj)
    55.     {
    56.          chestObj.gameObject.SetActive(false);
    57.     }
    58.  
    59.     void actionOnDestroy(ChestPool chestObj)
    60.     {
    61.          Destroy(chestObj.gameObject);
    62.          Debug.Log("Destroy Called");
    63.     }
    64.  
    65. }
    66.  
    67. //This is my spawn pool code, it it inside a empty game object and i wanted it to spawn objects using object pooling. The script responsible for returning objects to the pool is inside the object i want to return. And it's the following:
    68.  
    69. using System.Collections;
    70. using System.Collections.Generic;
    71. using UnityEngine;
    72. using UnityEngine.Pool;
    73.  
    74.  
    75. public class ChestPool : MonoBehaviour
    76. {  
    77.  
    78.     public ObjectPool<ChestPool> _pool;
    79.  
    80.    private void OnTriggerEnter(Collider other)
    81.    {
    82.     if(other.gameObject.CompareTag("floor")){
    83.         _pool.Release(this);
    84.     }
    85.    }
    86.  
    87.    public void SetPool(ObjectPool<ChestPool> pool){
    88.     _pool = pool;
    89.    }
    90. }
    91.  
    92. //I have checked and it is returning objects to the pool
    It is my first time using this forum, and i did the post according to what i understood from the guidelines, sorry if there's anything wrong. Also i posted the entire code because i have no idea of where the error is. Thanks for the attention!
     
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,588
    Are you actually having problems with garbage collection spikes slowing your game down? Pooling will not improve your performance at all, unless you actually have the specific problem that pooling was designed to address.
     
    Bunny83, theokiwi and MaskedMouse like this.
  3. theokiwi

    theokiwi

    Joined:
    Jul 2, 2020
    Posts:
    4
    I had to the a pooling demonstration for a class so it's not exactly a game i just made it spawn objects to see how it works with pooling. What's bothering me is that in the profiler im getting the exactly same results for memory allocation, and that makes me think that there's something wrong with the way i'm building things.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,335
    Yep, that's how pooling usually works.

    I have no idea who started this miserable trend towards "YOOZ POOLING, BE FASTERZ BRO!!"

    Here's some more reading.

    The costs and issues associated with object pooling / pools:

    https://forum.unity.com/threads/object-pooling.1329729/#post-8405055

    https://forum.unity.com/threads/object-pooling-in-a-tower-defense-game.1076897/#post-6945089

    In very rare extremely-high-count object circumstances I have seen small benefits from pooling.

    In 100% of ALL circumstances, object pooling is a source of constant bugs and edge case disasters.
     
    bugfinders and theokiwi like this.
  5. theokiwi

    theokiwi

    Joined:
    Jul 2, 2020
    Posts:
    4
    Thank you!
     
    Kurt-Dekker likes this.
  6. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,129
    Besides garbage collection, pooling does increasingly make sense the more an object does on creation and destruction. Basically what code you run in Awake, Start, OnEnable, OnDisable and OnDestroy matters.

    It is generally advisable to keep code in those methods clean and simple, thus efficient. If for example you perform Resources.Load (or similar) in Start and you instantiate several such objects per frame then pooling may help. But it would be better if the objects wouldn't call that method, normally you can do that when the scene loads and provide access to the references with a singleton for example.
     
    MartinTilo likes this.
  7. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,408
    Do you happen to have one of these SpawnPool objects in each scene and load a new scene for each scene? If so, check if Instance is not null on start and reuse the existing one, instead of the new one. Otherwise you're just leaking memory. (You might also want to set the initial singleton to be DontDestroyOnLoad and destroy any superfluous pool coming after it. This does not look like a clean example of the Singleton pattern and the pitfalls of that pattern might be a part of your issue here)

    The pool will not remove the initial allocation time when your pool is empty, just on reallocation. So whenever your game restarts or a new scene is loaded, that's where it'll make a difference, so profile that, instead of the first allocation.

    That or preheat you pool by spawning an amount of objects you'll likely need and despawning it as part of some loading pattern if you know you have a moment where you can spend some extra time on this and then a stretch of time where you really don't want to take the hit of the allocations or Garbage Collection.

    Having profiled the living crap out of action heavy mobile games, I can tell you that pooling is not just a fad (*), but it is certainly easy to be done wrong.

    Also UnityEngine.Objects are more heavy to allocate and initialize than just straight up managed C# Objects, and often have associated costs that are not always cleanly shown at the moment of instantiation (e.g. due to delayed initializations or lazy one time allocations, e.g. for those GetComponent calls, which only allocate on the first call, if at all).

    (* Though it might have been more noticeable in Unity 2017 and pre Incremental GC. These days one might have to do two runs, one with pooling and Incremental GC off, and one without pooling and Incremental GC on, and checking overall frame performance in Profile Analyser to see the difference. Lastly, if you're near exclisively GPU bound, pooling might hardly make a difference)
     
    Last edited: Nov 21, 2023
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,335
    Correctly-engineered pooling can definitely help.

    Engineering requires first a measurement, then a choice of appropriate solution followed by iteration and repeated measurement towards an ultimate solution that actually gives a benefit on the intended target hardware.

    That's engineering.

    Amateur slapped-on "let'z learn to pool all the things!!!" almost always fails on every level: increased complexity, decreased reliability, and even sometimes decreased performance.

    "Software does not run in a magic fairy aether powered by the fevered dreams of CS PhDs." - Mike Acton
     
  9. MartinTilo

    MartinTilo

    Unity Technologies

    Joined:
    Aug 16, 2017
    Posts:
    2,408
    Agreed, but that's somewhat besides the point when one is specifically trying to learn how to do pooling right.

    Back to what could be wrong with the implementation:
    If your
    maxPoolSize
    (defaulting to 0 in the inspector since it's not initialized) is lower than the amount of Chests present at a given moment, then you'll also keep creating chests new, though then returning them to the pool should trigger the Destroy. Similarly if the
    defaultCapacity
    (defaulting to 0) is less than the amount likely needed, the initial spawns until you reach your peak pool size will keep allocating. Also, tidbit but: naming that MonoBehaviour ChestPool might be somewhat misleading as its more of a PooledChest, or just a Chest, while the SpawnPool is hardwired to just be a pool for spawning Chests at the moment, so it could be more accurate to call that one a ChestPool.

    Beyond all of this and as alluded to by the others, getting familiar with how to debug with your IDE (so that you can see the state of your pool as objects spawn and despawn) and the Profiler is crucial to, respectively, make the right calls on why something might not be working and what architectures to use and how to optimize them. For the use case of reducing GC.Allocs I'd recommend reading up on the Allocation Callstacks feature of the CPU Usage Profiler Module, as well as the manual pages on Managed Memory, the Garbage Collector (and sub-pages) and Unity Objects. You'll be wanting to know what precisely allocates, how much and why, and why and how that matters to performance. You might not need it immediately when starting out but with game programming but it is knowledge that will definitely come in handy and learning of and thinking about what's going on behind the easy facade that Unity builds up around these, while getting more familiar with the basics of making a game, will mean you'll have a far easier time spotting mistakes and potential performance issues, as well as checking if they will actually hurt you. For that, just always think of the worst case you can imagine your game could throw at that system and test and measure against that. If it doesn't even register, you've just learned what you don't need to care about and can calmly focus on integrating it in the least complex way so that can be easily maintained.

    Speaking of maintainability and patterns: http://gameprogrammingpatterns.com is a great resource and also has something on the Singleton pattern you used here.
     
    Last edited: Nov 22, 2023
    Lurking-Ninja and Rotary-Heart like this.