Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loop is much faster as a for loop than a while loop, then when statement is changed, the while loop is faster. What happens?

Tags:

performance

c#

Out of interest, I tested if there was any difference to a for loop and a while loop doing the same thing. What causes the while loop to take about 2-2.5 seconds longer on my computer (AMD Phenom II X6 1090T @ 3.20GHz) than the for loop? Aren't they doing the same thing? Do you get similar results?

Also, when I replace the x = null; statement from the loops with just an empty statement, the while loop will be significantly faster. What's going on here?

Sure, the number of iterations is pretty high, but isn't the difference still pretty significant?

static void Main(string[] args)
{
    String x;
    const Int64 FIVE_BN = 5000000000;
    Int64 i = 0;

    DateTime start = DateTime.Now;
    for (; FIVE_BN > i; i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    Console.Out.WriteLine(FIVE_BN.ToString() + " times (for): " + (DateTime.Now - start));

    i = 0;

    start = DateTime.Now;
    while(FIVE_BN > i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    Console.Out.WriteLine(FIVE_BN.ToString() + " times (while): " + (DateTime.Now - start));

    Console.Read();
    return;
}
like image 337
David S. Avatar asked Nov 29 '22 03:11

David S.


1 Answers

While this is entirely a micro optimization that would never be the performance bottleneck. It interesting that the two are actually different, interestingly when you extract methods both the loops with VS2010 I get the following:

private static String forLoop(ref Int64 i)
{
    String x;

    for (; FIVE_BN > i; i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    return x;
}

private static void whileloop(ref String x, ref Int64 i)
{
    while (FIVE_BN > i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
}

And that is quite interesting... it shows that the two functions are indeed different.

now when we replace the logic in the loop with ; we get the following extracted methods instead:

private static Int64 forLoopShort(Int64 i)
{

    for (; FIVE_BN > i; i++)
        ; //Replace with only ; in both loops and the for loop is faster
    return i;
}

private static Int64 whileLoopShort(Int64 i)
{

    while (FIVE_BN > i++)
        ; //Replace with only ; in both loops and the for loop is faster
    return i;
}

Which indicates why the loops run basically the same with this configuration.

To work out how they're different when inline (and not extracted into methods) we need to see what the optimized CLR coded looks like (though the optimizer might actually remove any significant differences between the two functions).. That's something for a later edit.

Edit:

The CIL reveals the differences:

The For loop has .maxstack 2 but the while loop has .maxstack 4, otherwise there's a little diference in the order of operations due to the fact that the increment for the while happens at the start of the loop but the for operation happens at the end of the loop (change the content of the loop to Console.WriteLine(i) and see that the While loop will print from 1 but the For loop will print from 0 (both do the same number of loop iterations though).

When the loop contents is just a ; both loops are 2 lines shorter in CIL with the following lines removed (for both loops):

IL_0006:  ldnull
IL_0007:  stloc.0

However when we build in release the code is very different:

The difference between x = null; and ; is nothing, for either of the loops, as the optimizer has noticed that the value never changes to being not-null.

The difference between the optimised for and while loops are as follows:

CIL for loop:

IL_0000:  ldc.i4.0
IL_0001:  conv.i8
IL_0002:  stloc.0
IL_0003:  br.s       IL_000a
IL_0005:  ldloc.0
IL_0006:  ldc.i4.1
IL_0007:  conv.i8
IL_0008:  add
IL_0009:  stloc.0
IL_000a:  ldc.i8     0x12a05f200
IL_0013:  ldloc.0
IL_0014:  bgt.s      IL_0005
IL_0016:  ret

And CIL while loop:

IL_0000:  ldc.i4.0
IL_0001:  conv.i8
IL_0002:  stloc.0
IL_0003:  ldc.i8     0x12a05f200
IL_000c:  ldloc.0
IL_000d:  dup
IL_000e:  ldc.i4.1
IL_000f:  conv.i8
IL_0010:  add
IL_0011:  stloc.0
IL_0012:  bgt.s      IL_0003
IL_0014:  ret

So we can see that an optimised while loop is faster than a for loop by 2 operations, however it uses more stack space.

The difference between these two seems entirely related to the difference in where the i++ occurs.

Indeed this is confirmed by making a new method:

private static void forLoopVeryShort()
{
    string x;
    Int64 i = 0;

    for (; FIVE_BN > i++;)
        ; //Replace with only ; in both loops and the for loop is faster
}

The CIL code for this for method when built (in either release or debug) is identical to that of the while loop.

There in lies your difference. For loops perform exactly the same as while loops when they're doing the exact same behaviour. The difference you noted is entirely due to running the code in debug and not release, combined with the JIT not always being as efficient as the release code optimizer.

I have enjoyed this question, I learnt something out of it; I hope others do as well. +1

like image 93
Seph Avatar answered Dec 22 '22 00:12

Seph