- Home /
Need help improving the performance of Transform.SetParent for 5000+ objects
I've been experiencing some severe performance issues using Transform.SetParent for many objects. From reading this blog post I've determined that this is not an unusual issue to encounter in Unity.
However the blog post hasn't really helped me figure out how to re-write my code in a more efficient manner. Basically what I currently have looks something like this...
GameObject myContainer = GameObject.Find("My Container");
foreach (var item in items) // can be 5000+ items
{
GameObject myPrefab = Instantiate(myPrefab) as GameObject;
myPrefab.transform.SetParent(myContainer.transform, false);
myPrefab.transform.localPosition = new Vector3(x, y, z); // x,y,z change in each iteration
}
...so what would be the correct way to write this in C# for the best performance? (Right now this can take over 10 seconds for 3-5k items.
Are you sure that the delay comes from SetParent? Instantiate is a rather slow process and depending on how complex the object is you instantiate it can take quite some time. For example if the prefab has colliders and / or a rigidbody component there's a lot additional processing.
Are you sure you need 5000+ instances at the same time? Your code seems to be pseudocode. What's item and how is it related to the prefabs you instantiate? What actual purpose do those instances have? What do they represent? Are they UI objects or something else?
$$anonymous$$y instantiated objects are primitive cubes with a box collider on them. If I comment out Transform.SetParent(), the code executes in half the time. Before I come up with ways around using 5000+ instances, I'd rather know if there are any simple (relatively) ways to improve performance.
$$anonymous$$aybe it helps a bit to pass the parent transform to the Instantiate function?
GameObject myPrefab = Instantiate(myPrefab, myContainer.transform) as GameObject;
I got to agree with Bunny83 though, > 5000 instances of anything are quite a lot, you need to figure some way to organize them. In any case you're going to have some precalculation time, you won't get around this that easy.
Here's some thaughts:
A good way to manage a huge amount of objects in 3d space is using Octrees but if this can help you depends on what you are trying to achieve.
If not all blocks are required to spawn right away, implement some kind of "lazy" loading where you only load what's close to the player.
If you want to remove and create new blocks, consider reusing the old ones ins$$anonymous$$d of reinstantiating, this will save you a lot of performance.
$$anonymous$$aybe you can provide a little more information, what all these cubes meant to do? Do they move all the time? Are you creating these only when your game starts or more often?
First i would use something other than GameObject.FInd, maybe find with tag but it would be even better with a reference.
Since u use C# you could use the Instantiate<GameObject>(myPrefab);
overload (maybe it doesn't matter but i guess it's there for a reason)
Do the instantiated objects have rigidbodies? As far as i know instantiating static colliders makes the physics engine rebuild it's data, maybe it does this after every iteration? Working from here, an object pool may speed things up.
I only use GameObject.Find once at the start to find the container. It's not in the loop.
Actually the single GameObject.Find call will only take milliseconds (probably even micro seconds). So that's not really relevant.
The generic version of Instantiate is usually automatically choosen. You don't need to specify the generic type manually. Since the source parameter of the generic instantiate is of type "T" the compiler automatically uses the type of the parameter.
Transform prefab;
var inst = Instantiate(prefab);
"inst" should have the type Transform in this case. So this should also compile without error:
Transform inst = Instantiate(prefab);
There is one rigidbody on the parent container, then all the children have one collider.
Answer by JedBeryll · Nov 24, 2017 at 01:28 PM
Instantiating split into several frames: Note that i haven't checked it so it may have issues. Let me know if it does and you can't figure out what's wrong.
usage: StartCoroutine(InstantiateInFrames(original, parent, 5000, 10));
public IEnumerator InstantiateInFrames(GameObject original, Transform parent, int instanceAmount, int framesUsed) {
int instancesPerFrame = Mathf.CeilToInt(instanceAmount / framesUsed);
int current = 0;
for (int i = 0; i < instanceAmount; i++) {
GameObject instance = Instantiate(original, parent);
//whatever else you need to do
current++;
if (current >= instancesPerFrame) {
current = 0;
yield return null;
}
}
}
No, unfortunately this just kept crashing Unity. I tried a few different approaches, but couldn't get it working.
@AzzyDude24601 Too many objects, Unity normally works with 200-300 objects, 5k is too much, you need to seriously think about the optimization.
In this forum post the Unity representative, Joachim_Ante, talks about large hierarchies in the >5000 range so it doesn't seem to be completely unheard of.
This code works perfectly fine for me... what errors are you getting?
I would check out the Unity Profiler if you haven't, it may help narrow down the cause of the slowdowns.
Instantiating split into several frames
That does not help solve the core of the issue; only masks it behind a buffer of frames. In fact, due to overhead, it slightly increases the problem.
@$$anonymous$$ultirezonator - 5k is not too much. There is no too much. It depends entirely on the combination of per-object optimization and strategy regarding the total of objects. You could have 1,000,000 objects on screen at one time if you wanted; and you could set the transform parent for each of them in a few frames; maybe even a single frame, depending on how you do it.
Answer by XenoRo · Dec 03, 2017 at 05:39 AM
Your problem is not with SetParent
. It's the code in general. With all due respect; it's horrendously slow, to the point where it would be fair to call it "anti-optimized".
GameObject myContainer = GameObject.Find("My Container");
I don't know where this is written, but it's probably in the wrong place. It should be done in Awake()
, like all reference-setups/dependency-injections (all Find
calls or stuff doing the same as Find
calls).
Find
has the potential of having to traverse every object in your scene before it finds what you're looking for. It should only be done once, whenever possible. To keep a reference to the object you would be finding indefinitely (for as long as you need it), you cache it; meaning you store it in a field of the class. (there are other ways to do it, but this is the main one;specially for a beginner)
GameObject myPrefab = Instantiate(myPrefab) as GameObject;
If you are dealing with large amount os objects (as low as 10 or as high as 100, depending on the "weight" of the object's initialization [speaking in simplified terms: how much stuff the objects' type has/defines]), you want pooling. Instantiation is slow; reusing objects is fast; so unless you're creating 5000+ "useless" objects, you want to do pooling so that you don't have to instantiate any of those "a second time".
Also, I'm pretty sure you don't need the as
; because myContainer
is already a GameObject
, and/or Find
can only find GameObject
s anyways.
myPrefab.transform.SetParent(myContainer.transform, false);
Since you're going to set a new position
to the objects anyway, you can and should set the parent
field**, instead of calling the SetParent
method. This avoids the overhead related to handling the worldPositionStays
parameter (the false
you're calling the method with), and just changes the Transform's parent
reference directly.
myPrefab.transform.localPosition = new Vector3(x, y, z);
I don't know what iteration you're talking about when you say that "x, y and z change every iteration", but it ain't in the foreach-loop. But even if that's the case, the new Vector3
part is bad. Vector3
is a struct, therefore a value-type, therefore a pass-by-value, therefore "immutable"; but it's "immutable" across assignments and as a method parameter; not in a local scope. You could, and should, totally cache that vector outside of the loop, and simply use vector.Set(x, y, z)
for each iteration (if it's really even necessary in the first place), instead of creating a new one for every item/iteration.
If after these optimizations you still get a noticeable hit in the game when doing this operation, then you can start thinking about splitting the job across multiple frames, and/or across multiple threads...
...nvm the second one, you're too beginner for multi-threading yet, trust me; give yourself a few projects and a couple of years of experience with "normal" unity before you even start messing with multi-threading in unity; it's full of caveats and gotchas, and you will regret it if you try to deal with it now... Multi-threading is powerful, but you need to learn to walk before you learn to run; otherwise you will stumble, fall, and probably break your neck; you have been warned.
Even after that, other improvements are likely doable (such as avoiding having objects with "inappropriate children", as was the case in the blog post you referenced), but would need more specific details about how your project is currently setup/organized, and/or your source-code.
Okay, well first off, thank you for all the information and taking the time to answer. I should point out the code is pseudo-typed, so my GameObject.Find is in my awake. I only wrote it to illustrate my usage of SetParent. As for what the code is actually doing...
In essence I am creating an image browser for virtual reality. The parent object is actually an empty game object serving as an interactable wall that the user can swipe left and right with their motion controller to scroll through all the images, which are my 1-10k instantiated objects.
Inside the loop, I call another function not written here which returns x, y and z integer based on the current count of the foreach loop. This is what controls the pattern the images appear in the wall (e.g. interlocking rows of 3 or 2, etc, etc - basically things I can't do with the standard grid layout component). I think you are correct in that I can re-write this code to have less overhead in setting the position and parent of the objects.
To conclude, your bold text introducing the concept of multi-threading, only to immediately retract the idea due to your assumption of my skill level, to me, seems to serve no purpose. Not that it matters, but I've actually been successfully mult-threading in Unity for the past 2 years, despite it not being thread safe. If you genuinely thought I was too inexperienced for multi-threading, why even broach the subject?
Your answer
Follow this Question
Related Questions
Setparent function dont work properly 0 Answers
Instantantiate relative to parents position 1 Answer
Problems with transform.parent in unity 4.6 2 Answers
How do I go up from child to parent? 1 Answer
Lag on set transform.parent 0 Answers