Loop Performance: For vs. Foreach vs. While
Today’s article expands on the previous loop test article to find out which kind of loop is truly fastest. Read on to find out!
For today’s test I started with the previous test and made a few modifications. First, I took Johnny Boy’s suggestion and added a while
loop in addition to for
and foreach
.
Second, I took kzarczynski’s suggestion and made all the loops actually do something with each element of the array or List
. Inspired by his articles, I switched from a plain object
to a Counter
class with a Value
field I could access. Each loop now increases a sum
local variable by the Value
field to force the VM to actually fetch each element.
Third, I moved the List
creation and access to after the array and related tests were done. This should help a bit to make sure the CPU cache is primed equally for each set of tests.
Here’s the final result script:
using System; using System.Collections.Generic; using UnityEngine; public class TestScript : MonoBehaviour { private const int NumIterations = 100000; private class Counter { public int Value; } private string report; void Start() { report = "Size,Array For,Array While,Array Foreach,List For,List While,List Foreach\n"; var sizes = new []{ 10,100,1000 }; foreach (var size in sizes) { report += Test(size); } } private string Test(int size) { var stopwatch = new System.Diagnostics.Stopwatch(); var sum = 0; var array = new Counter[size]; for (int i = 0; i < size; ++i) { array[i] = new Counter() { Value = i }; } stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { for (int i = 0, len = array.Length; i < len; ++i) { sum += array[i].Value; } } var arrayForTime = stopwatch.ElapsedMilliseconds; stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { var i = 0; var len = array.Length; while (i < len) { sum += array[i].Value; ++i; } } var arrayWhileTime = stopwatch.ElapsedMilliseconds; stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { foreach (var cur in array) { sum += cur.Value; } } var arrayForeachTime = stopwatch.ElapsedMilliseconds; var list = new List<Counter>(size); list.AddRange(array); stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { for (int i = 0, len = list.Count; i < len; ++i) { sum += list[i].Value; } } var listForTime = stopwatch.ElapsedMilliseconds; stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { var i = 0; var len = list.Count; while (i < len) { sum += list[i].Value; ++i; } } var listWhileTime = stopwatch.ElapsedMilliseconds; stopwatch.Reset(); stopwatch.Start(); for (var iteration = 0; iteration < NumIterations; ++iteration) { foreach (var cur in list) { sum += cur.Value; } } var listForeachTime = stopwatch.ElapsedMilliseconds; return size + "," + arrayForTime + "," + arrayWhileTime + "," + arrayForeachTime + "," + listForTime + "," + listWhileTime + "," + listForeachTime + "\n"; } void OnGUI() { GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report); } }
If you want to try out the test yourself, simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.11.4
- Unity 5.3.4f1, Mac OS X Standalone, x86_64, non-development
- 640×480, Fastest, Windowed
And here are the results I got:
Size | Array For | Array While | Array Foreach | List For | List While | List Foreach |
---|---|---|---|---|---|---|
10 | 3 | 2 | 2 | 3 | 3 | 15 |
100 | 25 | 25 | 26 | 33 | 36 | 88 |
1000 | 255 | 253 | 276 | 301 | 336 | 864 |
The results have changed quite a bit since last time! I suspect this is largely due to the switch to using the elements, but it’s tough to know for sure. In any case, the array tests now show almost no difference between the three loop types for 100 and 1000 elements. The for
loop is still strangely slower than the others for 10 element arrays, but the numbers are so small that it’s possible this is due to other reasons such as that test running first on a colder CPU cache.
The List
tests have changed a little, too. Rather than foreach
being massively faster than for
, it’s a much closer race. The foreach
loop is still fastest regardless of List
length, but not tremendously so. Still, beware the garbage creation that will take place when using foreach
on a List
. That garbage will need to be collected eventually and you’ll pay the cost at that point.
The overall takeaway here is that loop type doesn’t really matter for arrays. For List
, use for
or while
to avoid garbage creation. It’s unlikely that the speed penalty will matter much but the eventual GC hit will be killer.
What’s your preferred loop for arrays and lists? Let me know in the comments!
#1 by Roger on June 19th, 2016 ·
What about .ForEach and a lambda? Is that changing the numbers to much?
#2 by Roger on June 19th, 2016 ·
I just implement it with a lambda and a delegate method:
Here the extra code:
And the delegate is a class method:
#3 by jackson on June 20th, 2016 ·
I can confirm with my own results on the same machine as the article was written:
I have no idea how it’s so fast though. There must be something going on under the hood to optimize the calls through the delegate (e.g. lambda). I’ve made a note to follow up on this, perhaps in comparison to the series on iterators that I’m currently writing.
Thanks for pointing this out and posting your results!
#4 by sk205 on November 8th, 2017 ·
After i do some research ,i find the better sequence of performance is while for freach!!