Behind the scenes part 1 – The difference between the foreach and the for loop.

Behind The Scenes .NET

For quite some time I am developing in C#. De C# language is a language that i know and that I love. But as the most .NET developers know, the .NET framework is compiling all our code into something that is called the intermediate language. So if you really want to become an superman in any .NET language, then you need to know how your code is getting executed on the machines. This means that you need to take a look at the intermediate language.

I will try to post a couple of blog post in my new serie ‘Behind the scenes’. A serie in which we will take a look at some C# code and how the intermediate language looks like after compiling. But before we deep dive in the intermediate language, let me first give you a inside in how I compile my C# code and how I get the intermediate language of that compiled code.

It all starts with a simple .cs file. I create just one file on purpose and not a complete solution. This is to get the output exactly as the code and without any extra’s that a solution sometimes can add to your code. Then I startup ‘Developer Command Prompt for VS2015’. Within this tool I will us the ‘cd’ command to navigate to folder where I just created the .cs file. With the simple command ‘csc {FileName}.cs’ we can then compile only that file. The result will be a .exe file with the same name as the .cs. This is you’re compiled version of the .cs file. Now we can head over to IL Spy. We can download that tool and open our .exe file with that tool. With IL Spy you can view the original C# language or you can see the intermediate language. Check out the video: ILSpy Tour on YouTube if you want to know more about IL Spy.

So now we know how we can see the intermediate language. We can go on with the interesting part of this post. We can now compare 2 features that does exactly the same and we can check what the compiler does with this code. In this post we will take a look at the difference between the foreach and the for loop.

So it’s now finally time for some code. Below are the 2 cases that I’ve created in C#.

Case 1

using System;</code>

namespace TestCase
{
    public class Program
    {
        public static void Main()
        {
            string[] arrayOfProducts = new string[] { "Azure", "Team Services", "Microservices" };

            foreach(string product in arrayOfProducts)
            {
                Console.Write(product);
            }
        }
    }
}

Case 2

using System;</code>

namespace TestCase
{
    public class Program
    {
        public static void Main()
        {
            string[] arrayOfProducts = new string[] { "Azure", "Team Services", "Microservices" };

            for(int i = 0; i &lt; arrayOfProducts.Length; i++)
            {
                Console.Write(arrayOfProducts[i]);
            }
        }
    }
}

So we save those 2 code samples to 2 different .cs files. Then compile those C# files by executing the command ‘csc {FileName}.cs’ twice and there will be 2 .exe files in that same folder. Now we can open those .exe files with IL Spy and take a look at the intermediate language. Below are the same cases in intermediate language. Lets take a closer look to what it all does. I’ve wrote a comment behind every line explaining what it does.

Case 1

.namespace TestCase //Declare namespace
{
	.class public auto ansi beforefieldinit TestCase.Program //Declare the class Program in the namespace TestCase
		extends [mscorlib]System.Object //Because every class is an Object it extends the System.Object 
	{
		// Methods
		.method public hidebysig static 
			void Main () cil managed // Declaring the static void main within the Program class.
		{
			// Method begins at RVA 0x2050
			// Code size 63 (0x3f)
			.maxstack 4 //tells the Just In Time Compiler (JIT) to reserve a maximal of 4 stack places  
			.entrypoint //tells that this method is the start method of this applications
			.locals init ( //Initialize local variables
				[0] string[], //Initialize a string array for the array called arrayOfProducts
				[1] string[], //Initialize a string array to duplicate the arrayOfProducts
				[2] int32, //Initialize a int to keep track of where we are within the array
				[3] string //initialize a string for the value from the foreach loop called 'product'
			)

			IL_0000: nop //Does nothing
			IL_0001: ldc.i4.3 //Push value 3 on the stack. This is for the amount of items in the array
			IL_0002: newarr [mscorlib]System.String // Creates a new array of the type string.
			IL_0007: dup // Duplicate the value on the top of the stack.
			IL_0008: ldc.i4.0 // Push value 0 on the stack. This is the place in the array where the first value will be set
			IL_0009: ldstr "Azure" // Push a string object to the stack with the value "Azure"
			IL_000e: stelem.ref // Replace the value on the stack on place 0 with the value that it just pushed to the stack.
			IL_000f: dup // Duplicate the value on the top of the stack.
			IL_0010: ldc.i4.1 // Push value 1 on the stack. This is the place in the array where the second value will be set
			IL_0011: ldstr "Team Services" // Push a string object to the stack with the value "Team Services"
			IL_0016: stelem.ref // Replace the value on the stack on place 1 with the value that it just pushed to the stack.
			IL_0017: dup // Duplicate the value on the top of the stack.
			IL_0018: ldc.i4.2 // Push value 2 on the stack. This is the place in the array where the second value will be set
			IL_0019: ldstr "Microservices" // Push a string object to the stack with the value "Microservices"
			IL_001e: stelem.ref // Replace the value on the stack on place 2 with the value that it just pushed to the stack.
			IL_001f: stloc.0 // Pop the stack to the first local variable that is the first string array.
			IL_0020: nop // Does nothing
			IL_0021: ldloc.0 // loads the first variable to the stack
			IL_0022: stloc.1 // Push the stack to the second local variable that is the second string array. So now we have 2 the same string array's
			IL_0023: ldc.i4.0 // Push value 0 on the stack. This is the place in the array where the first value will be set
			IL_0024: stloc.2 // Pop the stack to the third variable what is a int32
			IL_0025: br.s IL_0038 // Branch to tho the target IL_0038
			// loop start (head: IL_0038)
				IL_0027: ldloc.1 // loads the second variable to the stack
				IL_0028: ldloc.2 // Loads the third variable to the stack
				IL_0029: ldelem.ref //Load the element at index (saved in the third variable) from the array (saved in the second variable) onto the top of the stack.
				IL_002a: stloc.3 // Pop the value from the stack into the fourth variable
				IL_002b: nop // Does nothing
				IL_002c: ldloc.3 // Loads the value from the fourth variable into the stack.
				IL_002d: call void [mscorlib]System.Console::Write(string) // Calls the function System.Console.Write with the string of the stack.
				IL_0032: nop // Does nothing
				IL_0033: nop // Does nothing
				IL_0034: ldloc.2  // Loads the third variable into the stack. 
				IL_0035: ldc.i4.1 // Push value 1 on the stack
				IL_0036: add // Add two values, returning a new value.
				IL_0037: stloc.2 // Pop the stack to value into the third variable

				IL_0038: ldloc.2 // Loads the third variable into the stack.
				IL_0039: ldloc.1 // Loads the second variable into the stack.
				IL_003a: ldlen // Push the length (of type native unsigned int) of array on the stack.
				IL_003b: conv.i4 //Convert to int32, pushing int32 on stack
				IL_003c: blt.s IL_0027 // Branch to tho the target IL_0027 if length is less then the lenght loaded at IL_0027
			// end loop

			IL_003e: ret //Return from method, possibly with a value.
		} // end of method Program::Main

		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x209b
			// Code size 8 (0x8)
			.maxstack 8  //tells the Just In Time Compiler (JIT) to reserve a maximal of 8 stack places  

			IL_0000: ldarg.0 // Load argument 0 onto the stack.
			IL_0001: call instance void [mscorlib]System.Object::.ctor() // Calls the constructor of the System.Object of which it inherits.
			IL_0006: nop // Does nothing
			IL_0007: ret //Return from method, possibly with a value.
		} // end of method Program::.ctor

	} // end of class TestCase.Program

}

Case 2

.namespace TestCase //Declare namespace
{
	.class public auto ansi beforefieldinit TestCase.Program //Declare the class Program in the namespace TestCase
		extends [mscorlib]System.Object //Because every class is an Object it extends the System.Object
	{
		// Methods
		.method public hidebysig static 
			void Main () cil managed  // Declaring the static void main within the Program class
		{
			// Method begins at RVA 0x2050
			// Code size 62 (0x3e)
			.maxstack 4 // tells the Just In Time Compiler (JIT) to reserve a maximal of 4 stack places  
			.entrypoint // tells that this method is the start method of this applications
			.locals init ( //Initialize local variables
				[0] string[], //Initialize a string array for the array called arrayOfProducts
				[1] int32, //Initialize a int to keep track of where we are within the array
				[2] bool //Initialize a bool to check if we are at the end of the for loop
			)

			IL_0000: nop //Does nothing
			IL_0001: ldc.i4.3 //Push value 3 on the stack. This is for the amount of items in the array
			IL_0002: newarr [mscorlib]System.String // Creates a new array of the type string.
			IL_0007: dup  // Duplicate the value on the top of the stack.
			IL_0008: ldc.i4.0 // Push value 0 on the stack. This is the place in the array where the first value will be set
			IL_0009: ldstr "Azure" // Push a string object to the stack with the value "Azure"
			IL_000e: stelem.ref // Replace the value on the stack on place 0 with the value that it just pushed to the stack.
			IL_000f: dup // Duplicate the value on the top of the stack.
			IL_0010: ldc.i4.1 // Push value 1 on the stack. This is the place in the array where the second value will be set
			IL_0011: ldstr "Team Services" // Push a string object to the stack with the value "Team Services"
			IL_0016: stelem.ref // Replace the value on the stack on place 1 with the value that it just pushed to the stack.
			IL_0017: dup // Duplicate the value on the top of the stack.
			IL_0018: ldc.i4.2 // Push value 2 on the stack. This is the place in the array where the second value will be set
			IL_0019: ldstr "Microservices" // Push a string object to the stack with the value "Microservices"
			IL_001e: stelem.ref // Replace the value on the stack on place 2 with the value that it just pushed to the stack.
			IL_001f: stloc.0 // Pop the stack to the first local variable that is the first string array.
			IL_0020: ldc.i4.0 // Push value 0 on the stack. This is the place in the array where the first value will be set
			IL_0021: stloc.1 // Pop the stack to the second variable what is a int32
			IL_0022: br.s IL_0033 // Branch to tho the target IL_0038
			// loop start (head: IL_0033)
				IL_0024: nop // Does nothing
				IL_0025: ldloc.0 // loads the first variable to the stack
				IL_0026: ldloc.1 // loads the second variable to the stack
				IL_0027: ldelem.ref //Load the element at index (saved in the second variable) from the array (saved in the first variable) onto the top of the stack.
				IL_0028: call void [mscorlib]System.Console::Write(string) // Calls the function System.Console.Write with the string of the stack.
				IL_002d: nop // Does nothing
				IL_002e: nop // Does nothing
				IL_002f: ldloc.1 // loads the second variable to the stack
				IL_0030: ldc.i4.1 // Push value 1 on the stack. This is the place in the array where the second value will be set
				IL_0031: add // Add two values, returning a new value.
				IL_0032: stloc.1 // Pop the stack to the second local variable that is the int32.

				IL_0033: ldloc.1 // loads the second variable to the stack
				IL_0034: ldloc.0 // loads the first variable to the stack
				IL_0035: ldlen // Push the length (of type native unsigned int) of array on the stack.
				IL_0036: conv.i4 //Convert to int32, pushing int32 on stack
				IL_0037: clt // Push 1 (of type int32) if value1 < value2, else push 0.
				IL_0039: stloc.2  // Pop the stack to the third local variable that is the bool.
				IL_003a: ldloc.2 // loads the third variable to the stack
				IL_003b: brtrue.s IL_0024 // Branch to tho the target IL_0024 if stack is true
			// end loop

			IL_003d: ret
		} // end of method Program::Main

		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x209b
			// Code size 8 (0x8)
			.maxstack 8  //tells the Just In Time Compiler (JIT) to reserve a maximal of 8 stack places  

			IL_0000: ldarg.0 // Load argument 0 onto the stack.
			IL_0001: call instance void [mscorlib]System.Object::.ctor() // Calls the constructor of the System.Object of which it inherits.
			IL_0006: nop // Does nothing
			IL_0007: ret //Return from method, possibly with a value.
		} // end of method Program::.ctor

	} // end of class TestCase.Program

}

So the first thing you maybe notice is that in the C# code there is no constructor. But if you take a look at the intermediate language you see that there is always a constructor “instance void .ctor () cil managed”. So to run a program there should be a constructor. But how nice is it that we don’t have to write it if we don’t need it. This keeps our code base small and clean.

But now for the difference between the foreach and the for loop. You can see that the Intermediate Language handle this loops different. With the foreach loop it creates a second array. In the loop it push the value from the first array into a string and write that string. Then it pushes that string into the second array. Then it compares the length of those two array’s to see if it still needs to go on. It only saves a int32 to know where it currently is within the array.

While in the for loop, the Intermediate Language creates only one array. In the loop it just loads the right value into the stack and write that string directly from the stack. Then it compares the saved int32 (that it saves to know where it is in the array) to the length of the array. If the saved int32 is less then the length of the array, then it loops again.

Conclusion
There are small difference between the foreach and the for loop. But testing the speed of both with 1000000 items in the array and only looping trough (no function in the loop) then you van see that it takes the foreach loop 11 millisecondes and that it takes the for loop 9 millisecondes. The 2 millisecondes difference is really a micro improvement. When this is the last optimization that you can do to your code base, then you already got good software. But in most situations, the users of you software will not notice the micro improvement and the small improvement will not weight up against the simplicity of a foreach loop.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s