views:

208

answers:

7

Hi there, a quick, simple question from me about for-loops.

Situation I'm currently writing some high-performance code when I suddenly was wondering how the for-loop actually behaves. I know I've stumbled across this before, but can't for the life of me find this info again :/

Still, my main concern was with the limiter. Say we have:

for(int i = 0; i < something.awesome; i++)
{
// Do cool stuff
}

Question Is something.awesome stored as an internal variable or is the loop constantly retrieving something.awesome to do the logic-check? Why I'm asking is of course because I need to loop through a lot of indexed stuff and I really don't want the extra function-call overhead for each pass.

However if something.awesome is only called once, then I'm going back under my happy rock! :)

+4  A: 

It evaluated every time. Try this in a simple console app:

public class MyClass
{
    public int Value
    {
        get
        {                
            Console.WriteLine("Value called");
            return 3;
        }
    }
}

Used in this way:

MyClass myClass = new MyClass();
for (int i = 0; i < myClass.Value; i++)
{                
}

Will result in three lines printed to the screen.

Update

So to avoid this, you could this:

int awesome = something.awesome;
for(int i = 0; i < awesome; i++)
{
// Do cool stuff
}
Martin Ingvar Kofoed Jensen
+1  A: 

something.awesome will be reevaluated every time you go through the loop.

It would be better to do this:

int limit = something.awesome;
for(int i = 0; i < limit; i++)
{
// Do cool stuff
}
Oded
+13  A: 

You can use a simple sample program to check the behaviour:

using System;

class Program
{
    static int GetUpperBound()
    {
        Console.WriteLine("GetUpperBound called.");
        return 5;
    }

    static void Main(string[] args)
    {
        for (int i = 0; i < GetUpperBound(); i++)
        {
            Console.WriteLine("Loop iteration {0}.", i);
        }
    }
}

The output is the following:

GetUpperBound called. 
Loop iteration 0. 
GetUpperBound called. 
Loop iteration 1. 
GetUpperBound called. 
Loop iteration 2. 
GetUpperBound called. 
Loop iteration 3. 
GetUpperBound called. 
Loop iteration 4. 
GetUpperBound called.

The details of this behaviour are described in the C# 4.0 Language Specification, section 8.3.3 (You will find the spec inside C:\Program Files\Microsoft Visual Studio 10.0\VC#\Specifications\1033):

A for statement is executed as follows:

  • If a for-initializer is present, the variable initializers or statement expressions are executed in the order they are written. This step is only performed once.

  • If a for-condition is present, it is evaluated.

  • If the for-condition is not present or if the evaluation yields true, control is transferred to the embedded statement. When and if control reaches the end point of the embedded statement (possibly from execution of a continue statement), the expressions of the for-iterator, if any, are evaluated in sequence, and then another iteration is performed, starting with evaluation of the for-condition in the step above.

  • If the for-condition is present and the evaluation yields false, control is transferred to the end point of the for statement.

0xA3
Thanks for the extra information! I had no idea the answer was in front of me all the time! :)
Robelirobban
+1  A: 

Each time the compiler will retrive the value of something.awesome and evaluate it

Pramodh
+1  A: 

The condition is evaluated each time, including retrieving the value of something.awesome. If you want to avoid that, set a temp variable to something.awesome and compare to the temp variable instead.

cHao
+3  A: 

If something.awesome is a field it is likely to be access each time round the loop as something in the body of the loop may update it. If the body of the loop is simple enough and does not call any methods (apart from methods the compiler inlines), then the compiler may be able to proved that it is safe to put the value of something.awesome in a register. Compiler writers used to go to a lot of afford to do this short of thing.

However these days it takes a very long time to access a value from main memory, but once the value has been read for the first time, it is coached by the CPU. Reading the value for a 2nd time from the CPU cache is a lot closer in speed to reading it from a register then reading it from main memory. It is not uncommon for a CPU cache to be hundreds of times faster than main memory.

Now if something.awesome is a property, then it is in effect a method call. The compiler will call the method each time round the loop. However if the property/method is only a few lines of code it may be inline by the compiler. Inlineing is when the compiler puts in a copy of the method code directly rather than calling the method, so a property that just return the value of a field will behave the same as the field example above.

Evan when the property is not inlined, it will be in the CPU cache after it has been called the first time. So ales it is very complex or the loop goes round a lot of times it take a lot longer to call the first time round the loop, maybe by more than a factor of 10.

In the old days, it used to be easy because all memory access and cpu actions took about the same time. These days the processor cache can easily change the timing for some memory accesses and method calls by over a factor of 100. Profilers tend to still assume that all memory access take the same time! So if you profile you will be told to make changes that may not have any effect in the real world.

Changing the code to:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
}

Will in some cases spread it up, but also makes it more complex. However

int limit = myArray.length; 
for(int i = 0; i < limit; i++) 
{ 
   myArray[i[ = xyn;
}

is slower then

for(int i = 0; i < myArray.length; i++) 
{ 
   myArray[i[ = xyn;
}

as .net check the bound of arrays each time they are accessed and have logic to remove the check when the loop is simple enough.

So it is best to keep the code simple and clear until you can prove there is a problem. You make a lot better gain by spending your time improving the overall design of a system, this is easy to do if the code you start with is simple.

Ian Ringrose
Thanks a lot for the great read! In my current situation, it was indeed an array, however I've come across instances before where it hasn't been one, ex. properties. But I will take "keeping it clean" under heavy consideration. Second-guessing my decisions is after all counter-productive! :)
Robelirobban
A: