Derek Harmon <loresayer@msn.com> wrote:
<snip>
[color=blue]
> In general, its advisable to move invariant logic out of the loop conditional.[/color]
Well, unless it harms readability...
[color=blue]
> Normally, it makes little difference when you're looping over the .Length of
> an array, because the IL is optimized for this situation:
>
> for ( int i = 0; i < myArray.Length; ++i)[/color]
I don't believe it's the IL which is optimised here - it's the JIT
compiler which can optimise things appropriately.
Indeed, I believe that when you're using simple code such as the above,
you can actually get a performance *hit* by moving the invariant out of
the loop. I believe the JIT compiler can (in certain situations)
recognise the IL generated from a construct such as the above and do
clever things like removing bounds checks if it knows that the variable
used to access the array won't change value. I believe such
optimisations aren't performed when the length is hoisted out of the
loop.
Here's a sample program:
using System;
public class Test
{
static void Main()
{
int[] x = new int[100000];
Hoisted(x);
NotHoisted(x);
}
static void Hoisted(int[] foo)
{
int n = foo.Length;
for (int i=0; i < n; i++)
{
foo[i] = foo[i]*2;
}
}
static void NotHoisted(int[] foo)
{
for (int i=0; i < foo.Length; i++)
{
foo[i] = foo[i]*2;
}
}
}
Using cordbg with JIT optimisations on, here's the code for Hoisted and
NotHoisted:
Hoisted:
[0000] push esi
[0001] mov esi,dword ptr [ecx+4]
[0004] xor edx,edx
[0006] cmp esi,0
[0009] jle 00000016
[000b] cmp edx,dword ptr [ecx+4]
[000e] jae 00000013
[0010] mov eax,dword ptr [ecx+edx*4+8]
[0014] add eax,eax
[0016] mov dword ptr [ecx+edx*4+8],eax
[001a] inc edx
[001b] cmp edx,esi
[001d] jl FFFFFFEE
[001f] pop esi
[0020] ret
[0021] xor ecx,ecx
[0023] call 724FF980
[0028] int 3
NotHoisted:
[0000] xor edx,edx
[0002] cmp dword ptr [ecx+4],0
[0006] jle 00000012
[0008] mov eax,dword ptr [ecx+edx*4+8]
[000c] add eax,eax
[000e] mov dword ptr [ecx+edx*4+8],eax
[0012] inc edx
[0013] cmp edx,dword ptr [ecx+4]
[0016] jl FFFFFFF2
[0018] ret
Here, it looks like the JIT compiler hasn't done the hoisting for us,
but it's removed the array bounds checking. In both cases, you still
end up with a single length check on each iteration, but (to my mind)
the NotHoisted C# code is more readable. Basically, you can't gauge
performance based on IL alone. (It's hard to do it from assembly, too,
but there we go...)
[color=blue]
> What's more, take a quantity that many developers might think of as being
> equivalent to an array's Length -- the Count property of a Collection like
> ArrayList. What is the cost of having Count in the conditional of a for-loop
> you may ask?
>
> Count is a virtual property, so there is very little in the way of optimization of
> IL here. A callvirt operation has to walk the vtable of (potentially) several
> accessor methods. Thus, the innocuous-looking access of the Count on
> a collection is easily one of the more expensive choices a developer can
> undertake in their for-loop conditionals.[/color]
It's still unlikely to end up being a bottleneck, and I view
readability as being *much* more important. To me, it's more
immediately clear what
for (int i=0; i < collection.Count; i++)
{
....
}
does than
int n = collection.Count;
for (int i=0; i < n; i++)
{
....
}
--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
If replying to the group, please do not mail me too