
After receiving some questions and talking it through with my coworkers at Caped Koala Studios (the edutainment company behind www.poraora.com) I’ve decided to expand on the For vs Foreach question. This is part two of the article and you can read the previous one here.
Today I’ve asked a friend to compile and run the code on his Macbook Pro and on a iPad 2. First I’ll present the results and then delve deeper with ILSpy trying to explain what we see. You can skip to the interesting part right away.
Statistics
Test machine:
- 32 GHz Intel Core i7
- OS X Yosemite 10.10.4
- Unity 5.1.2f1, Mac OS X Standalone, x86_64, non-development,
- 640×480, Fastest, Windowed
Size | Array For | Array Foreach | List For | List Foreach |
---|---|---|---|---|
100 | 12 | 26 | 39 | 98 |
1000 | 121 | 270 | 354 | 901 |
Similarly like with windows build in the x86 version the for and foreach perform very similarly:
Size | Array For | Array Foreach | List For | List Foreach |
---|---|---|---|---|
100 | 13 | 13 | 52 | 121 |
1000 | 131 | 142 | 484 | 1067 |
And the results for iPad 2:
Size | Array For | Array Foreach | List For | List Foreach |
---|---|---|---|---|
100 | 126 | 197 | 533 | 1097 |
1000 | 1240 | 1964 | 5284 | 10135 |
The interesting part
So what’s really happening here?
I’ve went on and used the ILSpy to look under the hood. Looking at the C# generated from the decompiled IL (just for the sake of readability) we can see something interesting here.
Array For loop Source:
1 2 3 4 5 6 7 8 |
for (var iteration = 0; iteration < NumIterations; ++iteration) { for (int i = 0, len = array.Length; i < len; ++i) { obj = array[i]; obj.Value++; } } |
Array For loop ILSpy decompiled:
1 2 3 4 5 6 7 8 9 10 11 |
for (int j = 0; j < 100000; j++) { int k = 0; int num = array.Length; while (k < num) { ForVsForeach.Counter counter = array[k]; counter.Value++; k++; } } |
Nothing too shocking happening here. It’s worth to note that we have nicely cached the array.Length here so that the property accessor is called only once. Exchanging for with while is just notation difference really. So let’s look at the array foreach now.
Array Foreach loop Source:
1 2 3 4 5 6 7 |
for (var iteration = 0; iteration < NumIterations; ++iteration) { foreach (var cur in array) { cur.Value++; }http://blog.kzarczynski.com/wp-admin/profile.php } |
Array Foreach loop ILSpy decompiled:
1 2 3 4 5 6 7 8 9 |
for (int l = 0; l < 100000; l++) { ForVsForeach.Counter[] array2 = array; for (int m = 0; m < array2.Length; m++) { ForVsForeach.Counter counter2 = array2[m]; counter2.Value++; } } |
Not too suprisingly the array foreach is just a language feature. Under the hood it’s translated into a simple for but with two differences to actually using for:
- We get a copy of the array pointer
- We don’t cache the array.Length so we call the property accessor every iteration
This is the possible explanation of why the array foreach is slower than for – but why do we see the difference only with the 64b version?
Conclusions
- The array foreach is slower than using for but the difference is not as big as with lists and it seems not to affect the 32b builds as much as the 64b ones.
- The list for is slower than the array’s because of the item accessor performing additional checks.
- The List foreach is way slower than the for because it calls lots of virtual methods during the iteration. Like the List’s GetEnumerator() (which actually allocates the enumerator) and then the GetCurrent() and MoveNext() methods of the enumerator. You can read more about the Array vs List performance on Jackson Dunstan’s blog which is well worth following.
The main take away from this article is: For the time being, it’s safe to just keep using for as the loop of choice in performance critical areas.
Thanks for reading :)
Very interesting results! I too was curious why there is a difference between your results and mine, so I ran my original program through ILSpy to see. Here’s the array for loop:
int j = 0;
int num = array.Length;
while (j < num)
{
object obj = array[j];
j++;
}
That looks pretty much like yours, except that the counter increment isn't there. Still, the compiler didn't recognize that the loop does nothing and remove it entirely, though that could still be done by the VM.
Now here's the foreach loop over the array:
object[] array2 = array;
for (int l = 0; l < array2.Length; l++)
{
object obj2 = array2[l];
}
This version also ended up pretty much like yours, again minus the counter increment. Again, the whole loop wasn't stripped out by the compiler.
So I wonder- to what do you attribute the performance differences between our two versions?
Actually, I’ve just tried again and have run the code exactly as is from your blog post.
The result:
Size,Array For Time,Array Foreach Time,List For Time,List Foreach Time
10,0,1,3,18
100,7,11,32,90
1000,68,93,326,831
The performance is actually similar to what I get in the slightly changed code tested above.
I don’t understand how did you get array foreach to be faster than for in your tests. Any ideas?