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

For loop vs Foreach loop in Unity - which is faster

Discussion in 'Scripting' started by Maccyfin, Jun 19, 2020.

  1. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Hey all. Just sharing an interesting little experiment i did yesterday. At least it was interesting for me :D

    I wanted to find out if a for loop or a foreach loop is faster.
    The results below show that a for loop runs faster overall compared to a foreach loop. The lower the dot on the screen, the faster the operation time.


    Here's the code I used to time the operations:

    Code (csharp):
    1.  
    2.  
    3. for (int i = 0; i < m_fChartHolderWidth; i++)
    4. {
    5. for (int j = 0; j < actions.Length; j++)
    6. {
    7. stopWatch.Reset();
    8. stopWatch.Start();
    9.  
    10. //call the function we want to time
    11. actions[j]();
    12.  
    13. stopWatch.Stop();
    14. PlotPoint(i, stopWatch.ElapsedMilliseconds, m_PointColors[j]);
    15.  
    16. m_lTotalTimes[j] += stopWatch.ElapsedMilliseconds;
    17. m_lAvgTimes[j] = m_lTotalTimes[j] / (i + 1);
    18.  
    19. }
    20.  
    21. UpdateStatsText();
    22. m_iNOfTests += 1;
    23.  
    24. //so that the point gets drawn on the screen
    25. yield return new WaitForEndOfFrame();
    26. }
    27.  
    28.  
    And here's the 2 functions I tested. The for loop:

    Code (csharp):
    1.  
    2. private void ForLoopOperation()
    3. {
    4. int[] arrayOfInts = new int[10000000];
    5. for (int j = 0; j < arrayOfInts.Length; j++)
    6. {
    7.  
    8. }
    9. }
    10.  
    11.  
    And the foreach loop:
    Code (csharp):
    1.  
    2.  
    3. private void ForEachLoopOperation()
    4. {
    5. int[] arrayOfInts = new int[10000000];
    6. foreach (int number in arrayOfInts)
    7. {
    8.  
    9. }
    10. }
    11.  
    12.  
    So perhaps the learning point here is; if you're wanting to squeeze more performance out of your code, then actively use for loops in exchange foreach loops.

    It'll be good to hear what you guys think or if I've completely missed something out.

    Happy Developing
    Martin
     
  2. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    What is the time for the "for loop" if you put

    int number = arrayOfInts[j];

    within?
     
  3. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    You mean like this?

    Code (csharp):
    1.  
    2. private void ForLoopOperation()
    3. {
    4.  
    5. int[] arrayOfInts = new int[10000000];
    6.  
    7. for (int j = 0; j < arrayOfInts.Length; j++)
    8. {
    9. int number = arrayOfInts[j];
    10. }
    11. }
    12.  
    13.  
     
  4. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Interesting. I added the int iNumber assignment below:
    Code (CSharp):
    1.     private void ForLoopOperation()
    2.     {
    3.         int[] arrayOfInts = new int[10000000];
    4.         for (int j = 0; j < arrayOfInts.Length; j++)
    5.         {
    6.             int iNumber = arrayOfInts[j];
    7.         }
    8.     }
    9.  
    10.     private void ForEachLoopOperation()
    11.     {
    12.         int[] arrayOfInts = new int[10000000];
    13.         foreach (int number in arrayOfInts)
    14.         {
    15.             int iNumber = number;
    16.         }
    17.     }
    And the results were that the foreach loop is faster. Im guessing because the int already gets to the 'number' variable in the head of the loop.

     
  5. DaDonik

    DaDonik

    Joined:
    Jun 17, 2013
    Posts:
    258
    Try this instead. Should be what the foreach loop is doing internally.
    Code (CSharp):
    1.  
    2.     private void ForLoopOperation()
    3.     {
    4.         int[] arrayOfInts = new int[10000000];
    5.         int length = arrayOfInts.Length;
    6.         for (int j = 0; j < length; j++)
    7.         {
    8.             int iNumber = arrayOfInts[j];
    9.         }
    10.     }
     
  6. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    There's no reason to put a second "int iNumber = number" in the foreach loop. You're already getting that information.

    You should also get standard deviation for the two scores.

    And what's up with that spike?
     
  7. In the editor? Almost anything. :)
     
  8. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87

    Code (CSharp):
    1. private void ForLoopOperation()
    2.     {
    3.         int[] arrayOfInts = new int[10000000];
    4.         int length = arrayOfInts.Length;
    5.         for (int j = 0; j < length; j++)
    6.         {
    7.             int iNumber = arrayOfInts[j];
    8.         }
    9.     }

    Yeah I gave that try too. Looks like it makes very little difference on the for loop:


    Here's the updated code I tested:

    Code (CSharp):
    1.     private void ForLoopOperation()
    2.     {
    3.         int[] arrayOfInts = new int[10000000];
    4.         for (int j = 0; j < arrayOfInts.Length; j++)
    5.         {
    6.             int iNumber = arrayOfInts[j];
    7.         }
    8.     }
    9.  
    10.     private void ForEachLoopOperation()
    11.     {
    12.         int[] arrayOfInts = new int[10000000];
    13.         foreach (int number in arrayOfInts)
    14.         {
    15.             int iNumber = number;
    16.         }
    17.     }
    18.  
    19.     private void ForLoopWithCachedLengthOperation()
    20.     {
    21.         int[] arrayOfInts = new int[10000000];
    22.         int iLength = arrayOfInts.Length;
    23.         for (int j = 0; j < iLength; j++)
    24.         {
    25.             int iNumber = arrayOfInts[j];
    26.         }
    27.     }
     
    Last edited: Jun 19, 2020
  9. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    The second int iNumber in the foreach loop is just to access the value we've got in the array so that the loop is actually doing something. This is because there's no point in testing an empty for loop, we have to test what its accessing.

    What is standard deviation of the two scores? No sure what you mean by this.
     
  10. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    You are. By having "foreach (int number in arrayOfInts)", you're already accessing that integer. The second operation (int iNumber = number) only exists to make it slower, it doesn't access anything new.

    Standard deviation of the average from all scores. You know, 39 ms +/- ?? ms.

    Also the fact that your for loop's time gets higher each time is very weird.

    this:

    Code (CSharp):
    1.    
    2.     void Awake()
    3.     {
    4.         Task.Run(() =>
    5.         {
    6.             RunTest();
    7.         });
    8.     }
    9.  
    10.     void RunTest()
    11.     {
    12.         int times = 1000;
    13.         Stopwatch sw = new Stopwatch();
    14.         sw.Restart();
    15.         for (int i = 0; i < times; i++)
    16.         {
    17.             ForLoopOperation();
    18.         }
    19.         sw.Stop();
    20.         UnityEngine.Debug.Log("ForLoopOperation: " + sw.ElapsedMilliseconds / (double)times);
    21.  
    22.         sw.Restart();
    23.         for (int i = 0; i < times; i++)
    24.         {
    25.             ForEachLoopOperation();
    26.         }
    27.         sw.Stop();
    28.         UnityEngine.Debug.Log("ForEachLoopOperation: " + sw.ElapsedMilliseconds / (double)times);
    29.  
    30.         sw.Restart();
    31.         for (int i = 0; i < times; i++)
    32.         {
    33.             ForEachLoopWithSecondDeclarationOperation();
    34.         }
    35.         sw.Stop();
    36.         UnityEngine.Debug.Log("ForEachLoopWithSecondDeclarationOperation: " + sw.ElapsedMilliseconds / (double)times);
    37.  
    38.         sw.Restart();
    39.         for (int i = 0; i < times; i++)
    40.         {
    41.             ForLoopWithCachedLengthOperation();
    42.         }
    43.         sw.Stop();
    44.         UnityEngine.Debug.Log("ForLoopWithCachedLengthOperation: " + sw.ElapsedMilliseconds / (double)times);
    45.     }
    46.  
    47.     private void ForLoopOperation()
    48.     {
    49.         int[] arrayOfInts = new int[10000000];
    50.         for (int j = 0; j < arrayOfInts.Length; j++)
    51.         {
    52.             int iNumber = arrayOfInts[j];
    53.         }
    54.     }
    55.  
    56.     private void ForEachLoopOperation()
    57.     {
    58.         int[] arrayOfInts = new int[10000000];
    59.         foreach (int number in arrayOfInts)
    60.         {
    61.             //int iNumber = number;
    62.         }
    63.     }
    64.     private void ForEachLoopWithSecondDeclarationOperation()
    65.     {
    66.         int[] arrayOfInts = new int[10000000];
    67.         foreach (int number in arrayOfInts)
    68.         {
    69.             int iNumber = number;
    70.         }
    71.     }
    72.  
    73.     private void ForLoopWithCachedLengthOperation()
    74.     {
    75.         int[] arrayOfInts = new int[10000000];
    76.         int iLength = arrayOfInts.Length;
    77.         for (int j = 0; j < iLength; j++)
    78.         {
    79.             int iNumber = arrayOfInts[j];
    80.         }
    81.     }
    gives me:

    speed.PNG
     
  11. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    The main cost for real world scenarios is memory access not the loop instructions. So what really matters is layout of your data structure and how it's accessed inside the loop.

    Foreach vs for should basically almost never be a consideration in Unity now. If it's significant enough to make a difference then you should be using burst because the bang for buck is huge in comparison. And it doesn't support foreach. So in practice the whole foreach vs for is almost moot.
     
  12. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,042
    Indeed. And running a for/foreach loop on 10 million of anything in a game is likely a result of poor design. the 3-4ms could easily be recovered elsewhere. If your architecture has something like this, there is probably much worse stuff. The difference on a for/foreach loop on a realistic number is insignificant.
     
  13. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    This is completely academic for reasons already raised by others. But since you're looking into this anyway... isn't accessing the .Length variable (property?) of your arrays a concern here?
     
  14. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,850
    I use for loops most of the time as I am accessing a member by index in it I want to assign elsewhere. Once accessed then I break out of the loop.
     
    Ryiah likes this.
  15. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Yeah that makes sense about the int in the foreach loop being accessed.
    Thanks for doing the test as well. Looks like Foreach is overall faster in general.
     
  16. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    That's interesting that burst compilation doesn't support foreach, I hadn't considered that.
     
  17. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    True true. This was more an interesting experiment than actual code optimisation.
     
  18. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Accessing the .Length variable of the array or caching the length seemed to make very little difference, even with a 10,000,000 loop count. Good to check though as I'd had this conversations with some former colleagues about whether we should cache the length value or not and that in the for loop, but it makes very little difference.
     
  19. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Same here, and the same for a foreach too. I always break out early where possible.
     
  20. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    Thanks for all the replies guys. I'm going to do some more of these tests and post in this section of the forum and also on my forum here: https://www.howtomakemobilegames.com/?forum=379452

    A few of them include the speed of:
    - Arrays vs lists
    - struct vs class
    - string = "" vs string.empty

    Any other suggestions please let me know, will be good to experiment.
     
  21. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,850
    You should include builtin Arrays which have a fixed number. I am interested in structs versus classes. I am not too familiar with structs as they seem too strict such as I cannot reference methods from other classes which I do often. I also saw something about defining strings that are concatenated using the + sign versus setting them using the $ and saw a several seconds time difference between them in a million replication loop using the $ sign with quotes and {} brackets.
     
  22. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    I haven't worked with unity in a long time, is it still an issue that any form of gc allocations will sooner or later trigger a gc.collect() that causes a micro stutter in the game? People aren't sensitive to these things to the same degree, but some gamers (myself included) are really bothered by those microstutters and learned to spot them as telltale sign of poorly engineered (Unity) games. Every time I played a Unity game that suffered from those noticable stutters I wondered if those could have been prevented if they had cared about avoiding those allocations in the first place and manually triggeringe the gc.collect() at strategic times to avoid it being auto-triggered during any kind of player controlled movement. But then again, I probably care too much about optimization...
     
  23. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,884
    Honestly, I'm not sure it's always the developers fault at times. I've legit thought I was having problems with something in my game, so I'd go start a new scene (for testing). And completely empty, not even a camera and I'd be constantly skipping between 20 and 160FPS for like 30 second periods at random times.
     
  24. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    You can't compare the editor with a compiled build. Or did you see that happen in an empty scene that you compiled into a build?
     
  25. EternalAmbiguity

    EternalAmbiguity

    Joined:
    Dec 27, 2014
    Posts:
    3,144
    Even in the editor that sounds insane. An empty scene's FPS should be above 1000, not 100.
     
  26. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,051
    In c# accessing the .length variable is normally preferred as from what I remember reading it can allow the compiler to make some optimizations in terms of reduced bound checking, but if you provide a cached value it can't guarantee its within bounds. In other languages this is not always the case and sometimes caching could lead to substantial benefits. However we also have to be careful with this in terms of mono vs il2cpp.

    Having said that I quite often still cache the value due to habit ;)
     
  27. Maccyfin

    Maccyfin

    Joined:
    Aug 19, 2012
    Posts:
    87
    MostHated and Joe-Censored like this.