Instantiate copy takes super long after spawning it in nested: 800ms and 2MB GC Alloc?

Hi,

I’m using Unity 2020.3.30f

This is really weird to me, I hope someone can share some insight on this.

In my game I use a script to create 8 “ghosts” which are used to wrap objects when they go offscreen.

Everything was working fine before I made a small change to the code to make things more performant. And somehow I made it almost 100x worse, by setting a grandparent before spawning the child.

The hiearchy:

  • Ghosts
  • Ghost 1
  • prefab instance
  • Ghost 2
  • prefab instance
  • etc…

This is the code, I commented in CAPS the part te causes the issue:

protected virtual void CreateGhosts()
{
    Debug.Log("Create Ghosts "+gameObject);
    m_GhostParent = new GameObject($"Ghosts - {name}").transform;
    m_GhostParent.localPosition = Vector3.zero;
    m_GhostParent.localRotation = Quaternion.identity;
   
    for(int i = 0; i < 8; i++) {
        var originalGhostChild = m_GhostPrefab == null ? gameObject : m_GhostPrefab;
       
        GameObject ghostNesterInstance = Instantiate(m_GhostParent.gameObject, Vector3.zero, Quaternion.identity);
        ghostNesterInstance.name = $"{originalGhostChild.name} Ghost {i}";
        ghostNesterInstance.layer = originalGhostChild.layer;
        ghostNesterInstance.tag = originalGhostChild.tag;
        m_GhostTransforms[i] = ghostNesterInstance.transform;
       
        // THIS LINE RIGHT HERE causes the instantiate below to take 800ms instead of 16ms
        //m_GhostTransforms[i].SetParent(m_GhostParent);
       
        var ghostElement = ghostNesterInstance.AddComponent<ScreenWrapBehaviorGhostElement>();
        ghostElement.SetUp(this);
        m_Ghosts[i] = ghostElement;
        // Now create the instance of the child to set under the ghost element.
        Transform ghostChildInstance = Instantiate(originalGhostChild, m_GhostTransforms[i]).transform;
        ghostChildInstance.position = Vector3.zero;
       
        if ((m_CircleCollider != null || m_BoxCollider != null)
            && m_Rigidbody != null && m_DamageOnTouch != null) {
            SetupGhostCollider(ghostNesterInstance);
        }
        SetupGhostChild(i, ghostChildInstance);
    }
   
    // Set all ghost under parent: I DO THIS AT THE END BECAUSE IT DOESN'T CAUSE ISSUES
    for(int i = 0; i < m_GhostTransforms.Length; i++) {
        m_GhostTransforms[i].SetParent(m_GhostParent);
    }
   
    PositionGhost();
}

I’m just very curious why it happens.

In any case 16ms is way too long for spawning ghosts anyways, so I plan to refactor my code to only have a single ghost that teleports in the closest edge. And I’ll design my levels to avoid warping in corners.
That should make everything more performant.

(if there are other tips to make it more performants, ideas are welcomed. I need to make it as performant as possible since all objects: characters, projectiles, etc… can wrap)

Maybe look into setting the Transform’s hierarchyCapacity at the beginning?

But what part of it is actually taking most the time? You say setting the parent so does the profiler show the Transform system itself being shown as the dominating factor or any of the systems that are hooked into the transform-hierarchy change?

For instance, if you have active 2D colliders then they need to be detatched from their existing parent Rigidbody2D and attached to the new one (if it’s different) or if there’s no Rigidbody2D. This means the collider geometry will need recreating. You can stop this happening by making sure that the GameObject is inactive so you don’t get all this live-change stuff happening while you’re restructuring stuff.

Obviously knowing which line is a good start but you need to gather deeper information.

I see a bit issue with your code, even though I just glanced over it. Each iteration you create a new ghostNesterInstance which is an instance of your “m_GhostParent” object. However you add this new instance as a child to that same m_GhostParent object. So in the second iteration you would duplicate that now nested object with the parent and nest the result again. That means your objects grow exponentially. So you have

O
O(O)
O(O, O(O))
O(O, O(O), O(O, O(O)))
O(O, O(O), O(O, O(O)), O(O, O(O), O(O, O(O))))
O(O, O(O), O(O, O(O)), O(O, O(O), O(O, O(O))), O(O, O(O), O(O, O(O)), O(O, O(O), O(O, O(O)))))
// ...

In brackets I put the child objects. O is your original parent object and each iteration you create a copy of that object and add it as a child. I don’t think this makes much sense and could already explain the explosion of memory usage. Have you actually checked the hierarchy after your code ran? Or did Unity crash?

Duplicating objects that are already in the scene is in general risky as you copy the object as it is at that moment with all the state and changes which may have been applied to it. Why do you even instantiate a parent object? Isn’t that just for organisation? Don’t you have a prefab that you want to instantiate?

So in the second iteration you would duplicate that now nested object with the parent and nest the result again. That means your objects grow exponentially. So you have

Well you’re absolutely correct… I must have been really tired to miss that.

I think it might be a combination of what MelvMay mentioned too.

In the end I completely rewrote the script to instantiate only 2 “ghosts” one vertical one horizontal. And I Made it such that the “hierachy organization” part of script that Bunny83 mentioned, only happens in the Unity Editor. This should ensure I keep things clean in the editor to find stuff in the hierachy when I’m debugging those ghosts, and at the same time it’s as performant as possible in the build.

Thank you all for the advice!

1 Like