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

Accessing a script on 1000 GameObjects in a loop - Slow, how can I speed it up?

Discussion in 'Scripting' started by Dyonisian, Jun 13, 2018.

  1. Dyonisian

    Dyonisian

    Joined:
    Jan 21, 2015
    Posts:
    5
    Solved, thank you. The problem was that calling yield return null at the end of the loop in the coroutine made it process one index per frame. So the speed was bound by the framerate. Calling multiple functions per frame fixes the problem.

    Hi, I basically spawn a 1000ish objects which have a script and either a line or mesh renderer on them. I later try to loop through them to enable those mesh/line renderers. The reason is for creating an effect of seeing them all drawn one by one rather than having them all appear at the same time. However it seems its very slow to access all of these GameObjects and run a function on their scripts. It's in a coroutine because I want to be able to see them appear one by one, not all at once. The cost of the objects, the rendering, and the actual code being accessed does not seem to be a lot - its just that iterating through this list and calling a function on each GameObject seems slow - takes about 10-15 seconds, and this does not reduce a lot if I disable what the functions are actually doing.

    Is there an obvious solution I am missing here? If I want to trigger something on a 1000 GameObjects at once, would it better to use something like events or delegates? Is this a use case where ECS would be applicable, and result in a boost?

    Thanks

    Code -

    Code (CSharp):
    1.   IEnumerator AnimateNeurons()
    2.     {
    3.         if (!isRendered)
    4.         {
    5.             isTiming = true;
    6.             for(int i=0; i<somas.Length; i++)
    7.             {
    8.                 somas[i].EnableRenderer();
    9.                 //segments = neuron.transform.GetComponentsInChildren<SegmentAnimation>();
    10.                 //Debug.Log("Number of segments - " + segments.Length);
    11.  
    12.                 //foreach (SegmentAnimation seg in segments)
    13.                 //{
    14.                 //    seg.EnableRenderer();
    15.                 //    yield return null;
    16.                 //}
    17.                 yield return null;
    18.             }
    19.             isRendered = true;
    20.         }
    21.         Debug.Log("Finished coroutine, took " + timer);
    22.         yield return null;
    23.     }
    There are 302 somas, the function I call just enables individual mesh renderers. There's an inner loop I commented out which enables line renderers on children of the somas - which total to around a 1000. Its commented out because I tried doing that inside the soma scripts instead of in this loop, to see if it helped. But even iterating through 302 somas takes 10-15 seconds. I realize I might be using coroutines incorrectly, so that might be the issue.
     
    Last edited: Jun 13, 2018
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Could I suggest 2 things.

    First, you may want to include your coroutine (using code tags) just in case there is anything obvious there someone can spot.

    Second, when analysing performance issues, it's sometimes a good idea to start with the profiler. See if that can highlight any areas you may wish to focus on.
     
    hippocoder likes this.
  3. Dyonisian

    Dyonisian

    Joined:
    Jan 21, 2015
    Posts:
    5
    Code (CSharp):
    1.   IEnumerator AnimateNeurons()
    2.     {
    3.         if (!isRendered)
    4.         {
    5.             isTiming = true;
    6.             for(int i=0; i<somas.Length; i++)
    7.             {
    8.                 somas[i].EnableRenderer();
    9.                 //segments = neuron.transform.GetComponentsInChildren<SegmentAnimation>();
    10.                 //Debug.Log("Number of segments - " + segments.Length);
    11.  
    12.                 //foreach (SegmentAnimation seg in segments)
    13.                 //{
    14.                 //    seg.EnableRenderer();
    15.                 //    yield return null;
    16.                 //}
    17.                 yield return null;
    18.             }
    19.             isRendered = true;
    20.         }
    21.         Debug.Log("Finished coroutine, took " + timer);
    22.         yield return null;
    23.     }
    There are 302 somas, the function I call just enables individual mesh renderers. There's an inner loop I commented out which enables line renderers on children of the somas - which total to around a 1000. Its commented out because I tried doing that inside the soma scripts instead of in this loop, to see if it helped. But even iterating through 302 somas takes 10-15 seconds. I realize I might be using coroutines incorrectly, so that might be the issue.
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    That there should be completely fine, you're probably doing something else wrong. Use the profiler to see what's taking time.
     
    dadude123 likes this.
  5. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Well, if you have 1000 objects and process 1 per frame (using yield return null within that loop), it's absolutely logical that it takes the time you mentioned. Running at 60 FPS, you'd process 60 objects per second this way, 1000 would take roughly 16,67 seconds at a constant 60 FPS.

    Further "speed up" is impossible unless you process multiple objects per frame or manage to run at even higher FPS (it'll still take N frames, where N is the number of objects).

    You could either use a fixed size of objects to process per frame, no matter what's the total count.
    For instance, process 10 per frame. It'll roughly take 1,67 seconds at 60 FPS.

    Or you could say you want to limit the time it takes to T seconds, using NumObjects / T and you'll get the chunk size that you need to process per second in order to finish within T seconds. Next you want to approximate the number of objects to process per frame (probably an average value since FPS is usually not constant) so that'll be distributed quite evenly.

    For example, having 1000 objects with a processing time of 5 seconds, you can calculate that you need to process 200 objects per second. Next you'll try to figure out your current and/or average FPS and determine that you need to process 3-4 objects per frame.
     
    Last edited: Jun 13, 2018
    Dyonisian, Baste and Doug_B like this.
  6. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    I think that sounds like the approach to take.

    Maybe it could be possible to further deceive the eye into thinking they were individually spawning by using a random range based on this calculation. For example, in this case, each frame process between 0 and 8. Might be worth a try, see how it looks.
     
    Suddoha likes this.
  7. Dyonisian

    Dyonisian

    Joined:
    Jan 21, 2015
    Posts:
    5
    Thanks, someone else pointed this out too, and it makes perfect sense now that I think about it. I wasn't considering that the loop works once per frame only. I'm trying to fix it now, I'll post here as soon as it works (Which it probably will).

    Edit - Works, thank you. I call functions on multiple objects per frame, and set this according to the duration I want it to last and what the time since the last frame is.
     
    Last edited: Jun 13, 2018