c# one line of code causing bad Garbage Collection

Why is this one line causing such bad GC ?
______________________________________________________

NewObjectPool.instance.PoolObject(SpawnedObject);

Rest of the function the line of code in lives in:
_____________________________________________________

public virtual void ResetEffect ()
    {
        if(poolAfterComplete)
        {
            NewObjectPool.instance.PoolObject(SpawnedObject);
            //Debug.Log("Fish is returning back to pool");
        }

        else
        {
            return;
            Debug.Log("Object was pooled and is returning");
        }
    }

The line is referencing an public static in our object pool. So I decided to post the pool just in case:


using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class NewObjectPool : MonoBehaviour {

    /// <summary>
    /// Repository of commonly used prefabs.
    /// </summary>
    [AddComponentMenu("Gameplay/ObjectPool")]
    public static NewObjectPool instance { get; private set; }

    #region member
    /// <summary>
    /// Member class for a prefab entered into the object pool
    /// </summary>
    [System.Serializable]
    public class ObjectPoolEntry {
        /// <summary>
        /// the object to pre instantiate
        /// </summary>
        [SerializeField]
        public GameObject Prefab;
    
        /// <summary>
        /// quantity of object to pre-instantiate
        /// </summary>
        [SerializeField]
        public int Count;
    }
    #endregion

    /// <summary>
    /// The object prefabs which the pool can handle
    /// by The amount of objects of each type to buffer.
    /// </summary>
    public ObjectPoolEntry[] Entries;

    /// <summary>
    /// The pooled objects currently available.
    /// Indexed by the index of the objectPrefabs
    /// </summary>
    [HideInInspector]
    public List<GameObject>[] Pool;

    /// <summary>
    /// The container object that we will keep unused pooled objects so we dont clog up the editor with objects.
    /// </summary>
    protected GameObject ContainerObject;

    void OnEnable()
    {
        instance = this;
    }

    // Use this for initialization
    void Start()
    {
        ContainerObject = new GameObject("ObjectPool");
    
        //Loop through the object prefabs and make a new list for each one.
        //We do this because the pool can only support prefabs set to it in the editor,
        //so we can assume the lists of pooled objects are in the same order as object prefabs in the array
        Pool = new List<GameObject>[Entries.Length];
    
        for (int i = 0; i < Entries.Length; i++)
        {
            var objectPrefab = Entries[i];
        
            //create the repository
            Pool[i] = new List<GameObject>();
        
            //fill it
            for (int n = 0; n < objectPrefab.Count; n++)
            {
            
                var newObj = Instantiate(objectPrefab.Prefab) as GameObject;
            
                newObj.name = objectPrefab.Prefab.name;
            
                PoolObject(newObj);
            }
        }
    }

    /// <summary>
    /// Gets a new object for the name type provided.  If no object type exists or if onlypooled is true and there is no objects of that type in the pool
    /// then null will be returned.
    /// </summary>
    /// <returns>
    /// The object for type.
    /// </returns>
    /// <param name='objectType'>
    /// Object type.
    /// </param>
    /// <param name='onlyPooled'>
    /// If true, it will only return an object if there is one currently pooled.
    /// </param>
    public GameObject GetObjectForType(string objectType, bool onlyPooled)
    {
    
        for (int i = 0; i < Entries.Length; i++)
        {
            var prefab = Entries[i].Prefab;
        
            if (prefab.name != objectType)
                continue;
        
            if (Pool[i].Count > 0)
            {
            
                GameObject pooledObject = Pool[i][0];
            
                Pool[i].RemoveAt(0);
            
                pooledObject.transform.parent = null;
            
                pooledObject.SetActive(true);
            
                return pooledObject;
            }
            if (!onlyPooled)
            {
                GameObject newObj = Instantiate( Entries[ i ].Prefab ) as GameObject;
                newObj.name = Entries[ i ].Prefab.name;
                return newObj;
            }
        }
    
        //If we have gotten here either there was no object of the specified type or non were left in the pool with onlyPooled set to true
        return null;
    }

    /// <summary>
    /// Pools the object specified.  Will not be pooled if there is no prefab of that type.
    /// </summary>
    /// <param name='obj'>
    /// Object to be pooled.
    /// </param>
    public void PoolObject(GameObject obj)
    {
    
        for (int i = 0; i < Entries.Length; i++)
        {
            if (Entries[i].Prefab.name != obj.name)
                continue;
        
            obj.SetActive(false);
        
            obj.transform.parent = ContainerObject.transform;
        
            Pool[i].Add(obj);
        
            return;
        }
    }
}

Heres the profiler data showing the bad Garbage Collection:
____________________________________________________________________

My guess is that line #145 generates new strings.

  if (Entries[i].Prefab.name != obj.name)

And since it’s inside the loop, it generates many strings.

1 Like

Click on “deep profiling” and it should give you a lot more info on where the garbage is coming from.

1 Like

Ok, I did a test to see if it could be the loop generating new strings by creating a new test script and object that doesn’t use an coroutine: In it’s place I used an invoke.

Test script with suspect line of code:
__________________________________

using UnityEngine;
using System.Collections;

public class TestDestory : MonoBehaviour {

    public GameObject Self;

    // Use this for initialization
    void Start ()
    {
        Invoke ("DesotryTheTest", 5f);
   
    }
   
    // Update is called once per frame
    void DesotryTheTest ()
    {
        NewObjectPool.instance.PoolObject(Self);
    }
}

However despite the new code, the garbage was still very awful(suggesting it’s not the loop). While testing this with the deep profile on (just as Hidden Monk had suggested), the profiler pointed to “Object.get_name()”.
____________________________________________________________________________________________

2374967--161438--Screen Shot 2015-11-09 at 11.48.24 AM.png

Should I replace Get name with tag? would this be more performant? how would I implement get tag instead of name within the current object pool system?

What about caching the names?

    /// <summary>
    /// Member class for a prefab entered into the object pool
    /// </summary>
    [Serializable]
    public class ObjectPoolEntry
    {
        /// <summary>
        /// the object to pre instantiate
        /// </summary>
        [SerializeField]
        public GameObject Prefab;

        /// <summary>
        /// quantity of object to pre-instantiate
        /// </summary>
        [SerializeField]
        public int Count;

        private string name;
        public string Name // <======================================
        {
            get
            {
                if (name == null && Prefab != null)
                {
                    name = Prefab.name;
                }
                return name;
            }
        }
    }
    /// <summary>
    /// Pools the object specified.  Will not be pooled if there is no prefab of that type.
    /// </summary>
    /// <param name='obj'>
    /// Object to be pooled.
    /// </param>
    public void PoolObject(GameObject obj)
    {
        var objName = obj.name; // <======================================
        for (int i = 0; i < Entries.Length; i++)
        {
            if (Entries[i].Name != objName)
            {
                continue;
            }
            obj.SetActive(false);
            obj.transform.parent = ContainerObject.transform;
            Pool[i].Add(obj);
            return;
        }
    }

Wow, this did help allot the Garbage went from 15.68 KB to 3.8 KB. Still a little high but a drastic improvement ^_^.

Thanks allot Alexzzzz.

2375284--161468--Screen Shot 2015-11-09 at 3.18.41 PM.png

Hmm… 4 calls to PoolObject() and 68 calls to Object.get_name(). There should have been only 4 calls to Object.get_name(). Have you made all the changes?

I thought I did, but I could have easily missed something:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class NewObjectPool : MonoBehaviour
{
    /// <summary>
    /// Repository of commonly used prefabs.
    /// </summary>
    [AddComponentMenu("Gameplay/ObjectPool")]
    public static NewObjectPool instance { get; private set; }
   
    #region member
    /// <summary>
    /// Member class for a prefab entered into the object pool
    /// </summary>
    [System.Serializable]
    public class ObjectPoolEntry
    {
        /// <summary>
        /// the object to pre instantiate
        /// </summary>
        [SerializeField]
        public GameObject Prefab;
       
        /// <summary>
        /// quantity of object to pre-instantiate
        /// </summary>
        [SerializeField]
        public int Count;

        private string name;

        public string Name
        {
            get
            {
                if (name == null && Prefab != null)
                {
                    name = Prefab.name;
                }
                return name;
            }
        }
    }
    #endregion
   
    /// <summary>
    /// The object prefabs which the pool can handle
    /// by The amount of objects of each type to buffer.
    /// </summary>
    public ObjectPoolEntry[] Entries;
   
    /// <summary>
    /// The pooled objects currently available.
    /// Indexed by the index of the objectPrefabs
    /// </summary>
    [HideInInspector]
    public List<GameObject>[] Pool;
   
    /// <summary>
    /// The container object that we will keep unused pooled objects so we dont clog up the editor with objects.
    /// </summary>
    protected GameObject ContainerObject;
   
    void OnEnable()
    {
        instance = this;
    }
   
    // Use this for initialization
    void Start()
    {
        ContainerObject = new GameObject("ObjectPool");
       
        //Loop through the object prefabs and make a new list for each one.
        //We do this because the pool can only support prefabs set to it in the editor,
        //so we can assume the lists of pooled objects are in the same order as object prefabs in the array
        Pool = new List<GameObject>[Entries.Length];
       
        for (int i = 0; i < Entries.Length; i++)
        {
            var objectPrefab = Entries[i];
           
            //create the repository
            Pool[i] = new List<GameObject>(); 
           
            //fill it
            for (int n = 0; n < objectPrefab.Count; n++)
            {
               
                var newObj = Instantiate(objectPrefab.Prefab) as GameObject;
               
                newObj.name = objectPrefab.Prefab.name;
               
                PoolObject(newObj);
            }
        }
    }
   
    /// <summary>
    /// Gets a new object for the name type provided.  If no object type exists or if onlypooled is true and there is no objects of that type in the pool
    /// then null will be returned.
    /// </summary>
    /// <returns>
    /// The object for type.
    /// </returns>
    /// <param name='objectType'>
    /// Object type.
    /// </param>
    /// <param name='onlyPooled'>
    /// If true, it will only return an object if there is one currently pooled.
    /// </param>
    public GameObject GetObjectForType(string objectType, bool onlyPooled)
    {
       
        for (int i = 0; i < Entries.Length; i++)
        {
            var prefab = Entries[i].Prefab;
           
            if (prefab.name != objectType)
                continue;
           
            if (Pool[i].Count > 0)
            {
               
                GameObject pooledObject = Pool[i][0];
               
                Pool[i].RemoveAt(0);
               
                pooledObject.transform.parent = null;
               
                pooledObject.SetActive(true);
               
                return pooledObject;
            }
            if (!onlyPooled)
            {
                GameObject newObj = Instantiate( Entries[ i ].Prefab ) as GameObject;
                newObj.name = Entries[ i ].Prefab.name;
                return newObj;
            }
        }
       
        //If we have gotten here either there was no object of the specified type or non were left in the pool with onlyPooled set to true
        return null;
    }
   
    /// <summary>
    /// Pools the object specified.  Will not be pooled if there is no prefab of that type.
    /// </summary>
    /// <param name='obj'>
    /// Object to be pooled.
    /// </param>
    public void PoolObject(GameObject obj)
    {
        var objName = obj.name;

        for (int i = 0; i < Entries.Length; i++)
        {
            if (Entries[i].Prefab.name != objName)
            {
                continue;
            }
           
            obj.SetActive(false);
           
            obj.transform.parent = ContainerObject.transform;
           
            Pool[i].Add(obj);
           
            return;
        }
    }
}

Line #161

if (Entries[i].Name != objName)

Ok fixed line 161, the Garbage went down even more this time and the draw calls for Object.get_name(). One call per object it seems. Thanks allot for your help :).

2376786--161615--Screen Shot 2015-11-10 at 2.56.52 PM.png

1 Like