Introduction
In this article we will be taking a look at the Common Intermediate Language (CIL) that
is generated by the various language compilers in Visual Studio 2005.
What is the .NET Framework?
For purpose of brevity the answer the answer to this question would go as follows: The .NET framework is a software component that can be added to Microsoft Windows operating system (although there is some work in place for this to be able to run on Linux and other operating systems). The .NET Framework consists of a collection of classes that contain pre-coded solutions to common programming requirements. Applications that are developed for the .NET Framework run in a software environment that manages the program's runtime requirements. This runtime environment is known as the Common Language Runtime (CLR). The CLR provides the appearance of an
application virtual machine, so that programmers need not consider the capabilities of the specific CPU that will execute the program. The CLR also provides other important services such as security guarantees, memory management, and exception handling.
The runtime also accelerates developer productivity. For example, programmers can write applications in their development language of choice, yet take full advantage of the runtime, the class library, and components written in other languages by other developers. Any compiler vendor who chooses to target the runtime can do so. Language compilers that target the .NET Framework make the features of the .NET Framework available to existing code written in that language, greatly easing the migration process for existing applications.
- The most important component of the .NET Framework lies in the Common Language Infrastructure, or CLI. The purpose of the CLI is to provide a language-agnostic platform for application development.
To clarify, the CLI is a specification, not an implementation, and is often confused with the (CLR)
The CLI consists of 5 Primary parts:
- Common Type System (CTS)
- Common Language Specification (CLS)
- Common Intermediate Language (CIL)
- Just in Time Compiler (JIT)
- Virtual execution system (VES)
A detailed discussion of all 5 these parts is out of the scope of this article. However I feel some explanation of the following two components required
- Common Language Specification (CLS) - Is a set of specifications that Microsoft has supplied to help compiler vendors. These specifications dictate the minimum group of features that a .NET language must have.
- Common Intermediate Language (CIL) - is the lowest-level human-readable programming language in the Common Language Infrastructure and in the .NET Framework. Languages which target the .NET Framework compile to CIL, which is assembled into bytecode. CIL resembles an object oriented assembly language, and is entirely stack-based. It is executed by a virtual machine.
- Just In Time Compiler (JIT) - (JIT) compiling enables all managed code to run in the native machine language of the system on which it is executing. Meanwhile, the memory manager removes the possibilities of fragmented memory and increases memory locality-of-reference to further increase performance.
Are all .NET languages born equal?
Regardless of which .NET programming language is used, the output of the language compiler is a representation of the same logic in CIL. Before the program is executed, CIL is compiled to object code appropriate for the machine on which the program is executing. This last compilation step is usually performed by the CLR component of the .NET Framework at the moment the program invoked, though it can be manually performed at an earlier stage.
The Concept
In order to examine if there is actually a difference in the CIL compiled by the four compilers I developed four identical applications in the four Main languages offered in my version of Visual Studio 2005 (VB.NET, C#, C++ and J#). I followed the exact same steps in creating the applications. I Quadrifurcated the applications and threw in some bad habits. I would compiled all these applications then took a look at their MSIL compilers.
The Applications
I created console application that basically contained a stopwatch to time how long the application took to execute. The Application itself basically contained a Loop that iterated through a thousand times and concatenated a string. The result if the string concatenation was printed out to the console and also printed out the length of time it it took to execute.
I omitted the use of the StringBuilder
class, in all these applications, this was done intentionally, as this was also a test to see the efficiency of the StringBuilder
class. If you read Part 2 of this article you can see the performance gains supplied by the StringBuilder
class.
The Code
VB.NET
Imports System.Diagnostics
Module Module1
Sub Main()
Dim strSomeString As String = ""
Dim sw As New Stopwatch
sw.Start()
For i As Integer = 0 To 1000
strSomeString += "Test "
Next
sw.Stop()
Console.WriteLine(strSomeString)
Console.WriteLine(sw.ElapsedMilliseconds)
Console.ReadLine()
End Sub
End Module
C#
using System;
using System.Diagnostics;
namespace ConsoleCSharp
{
class Program
{
static void Main(string[] args)
{
string strSomeString = "";
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
strSomeString += "Test ";
}
sw.Stop();
Console.WriteLine(strSomeString);
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
J#
package ConsoleJSharp;
import System.*;
import System.Diagnostics.*;
public class Program
{
public static void main(String[] args)
{
String strSomeString = "";
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
strSomeString += "Test ";
}
sw.Stop();
Console.WriteLine(strSomeString);
Console.WriteLine(sw.get_ElapsedMilliseconds());
Console.ReadLine();
}
}
C++
#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;
using namespace System::Diagnostics;
int main(array<System::String ^> ^args)
{
String^ strSomeString = "";
Stopwatch^ sw = gcnew Stopwatch;
sw->Start();
for (int i = 0;i< 1000 ;i++)
{
strSomeString += "Test ";
}
sw->Stop();
Console::WriteLine(strSomeString);
Console::WriteLine(sw->ElapsedMilliseconds);
Console::ReadLine();
}
The compiled CIL
VB.NET
.method public static void Main() cil managed
{
.entrypoint
.custom instance void
[mscorlib]
System.STAThreadAttribute::.ctor()=(01 00 00 00)
// Code size 83 (0x53)
.maxstack 2
.locals init (
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i, [3] int32 VB$CG$t_i4$S0
)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: stloc.0
IL_0007: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0013: nop
IL_0014: ldc.i4.0
IL_0015: stloc.2
IL_0016: ldloc.0
IL_0017: ldstr "Test "
IL_001c: call string
[mscorlib]
System.String::Concat(string, string)
IL_0021: stloc.0
IL_0022: nop
IL_0023: ldloc.2
IL_0024: ldc.i4.1
IL_0025: add.ovf
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: ldc.i4 0x3e8
IL_002d: stloc.3
IL_002e: ldloc.3
IL_002f: ble.s IL_0016
IL_0031: ldloc.1
IL_0032: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_0037: nop
IL_0038: ldloc.0
IL_0039: call void
[mscorlib]
System.Console::WriteLine(string)
IL_003e: nop
IL_003f: ldloc.1
IL_0040: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0045: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004a: nop
IL_004b: call string
[mscorlib]
System.Console::ReadLine()
IL_0050: pop
IL_0051: nop
IL_0052: ret
} // end of method Module1::Main
C#
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i,
[3] bool CS$4$0000
)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: stloc.0
IL_0007: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000c: stloc.1
IL_000d: ldloc.1
IL_000e: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0013: nop
IL_0014: ldc.i4.0
IL_0015: stloc.2
IL_0016: br.s IL_002a
IL_0018: nop
IL_0019: ldloc.0
IL_001a: ldstr "Test "
IL_001f: call string
[mscorlib]
System.String::Concat(string,string)
IL_0024: stloc.0
IL_0025: nop
IL_0026: ldloc.2
IL_0027: ldc.i4.1
IL_0028: add
IL_0029: stloc.2
IL_002a: ldloc.2
IL_002b: ldc.i4 0x3e8
IL_0030: clt
IL_0032: stloc.3
IL_0033: ldloc.3
IL_0034: brtrue.s IL_0018
IL_0036: ldloc.1
IL_0037: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_003c: nop
IL_003d: ldloc.0
IL_003e: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0043: nop
IL_0044: ldloc.1
IL_0045: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_004a: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004f: nop
IL_0050: call string
[mscorlib]
System.Console::ReadLine()
IL_0055: pop
IL_0056: ret
}
J#
.method public hidebysig static void main(string[]args)cil managed
{
.entrypoint
// Code size 108 (0x6c)
.maxstack 2
.locals init
(
[0] string strSomeString,
[1] class [System]System.Diagnostics.Stopwatch sw,
[2] int32 i
)
IL_0000: ldtoken [vjslib]
com.ms.vjsharp.lang.ObjectImpl
IL_0005: call void
[mscorlib]
System.Runtime.CompilerServices.
RuntimeHelpers::RunClassConstructor
(
valuetype [mscorlib]System.RuntimeTypeHandle
)
IL_000a: ldstr ""
IL_000f: stloc.0
IL_0010: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_0015: stloc.1
IL_0016: ldloc.1
IL_0017: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_001c: ldc.i4.0
IL_001d: stloc.2
IL_001e: br.s IL_003f
IL_0020: newobj instance void
[vjslib]
java.lang.StringBuffer::.ctor()
IL_0025: ldloc.0
IL_0026: callvirt instance class
[vjslib]
java.lang.StringBuffer
[vjslib]
java.lang.StringBuffer::append(string)
IL_002b: ldstr "Test "
IL_0030: callvirt instance class
[vjslib]
java.lang.StringBuffer
[vjslib]
java.lang.StringBuffer::append(string)
IL_0035: callvirt instance string
[vjslib]
java.lang.StringBuffer::ToString()
IL_003a: stloc.0
IL_003b: ldloc.2
IL_003c: ldc.i4.1
IL_003d: add
IL_003e: stloc.2
IL_003f: ldloc.2
IL_0040: ldc.i4 0x3e8
IL_0045: blt.s IL_0020
IL_0047: ldloc.1
IL_0048: callvirt instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_004d: ldloc.0
IL_004e: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0053: ldloc.1
IL_0054: callvirt instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0059: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_005e: call string
[mscorlib]
System.Console::ReadLine()
IL_0063: pop
IL_0064: call void
[vjslib]
com.ms.vjsharp.util.Utilities::cleanupAfterMainReturns()
IL_0069: br.s IL_006b
IL_006b: ret
} // end of method Program::main
C++
.method assembly static int32 main(string[] args) cil managed
{
.maxstack 2
.locals (
[0] class [System]System.Diagnostics.Stopwatch sw,
[1] string strSomeString,
[2] int32 i
)
IL_0000: ldnull
IL_0001: stloc.1
IL_0002: ldnull
IL_0003: stloc.0
IL_0004: ldstr ""
IL_0009: stloc.1
IL_000a: newobj instance void
[System]
System.Diagnostics.Stopwatch::.ctor()
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: call instance void
[System]
System.Diagnostics.Stopwatch::Start()
IL_0016: ldc.i4.0
IL_0017: stloc.2
IL_0018: br.s IL_001e
IL_001a: ldloc.2
IL_001b: ldc.i4.1
IL_001c: add
IL_001d: stloc.2
IL_001e: ldloc.2
IL_001f: ldc.i4 0x3e8
IL_0024: bge.s IL_0034
IL_0026: ldloc.1
IL_0027: ldstr "Test "
IL_002c: call string
[mscorlib]
System.String::Concat
(
string,
string
)
IL_0031: stloc.1
IL_0032: br.s IL_001a
IL_0034: ldloc.0
IL_0035: call instance void
[System]
System.Diagnostics.Stopwatch::Stop()
IL_003a: ldloc.1
IL_003b: call void
[mscorlib]
System.Console::WriteLine(string)
IL_0040: ldloc.0
IL_0041: call instance int64
[System]
System.Diagnostics.Stopwatch::get_ElapsedMilliseconds()
IL_0046: call void
[mscorlib]
System.Console::WriteLine(int64)
IL_004b: call string
[mscorlib]
System.Console::ReadLine()
IL_0050: pop
IL_0051: ldc.i4.0
IL_0052: ret
}
Points to note
- There were slight differences in the output generated by the various compilers, especially at the start of each code segment.
- VB and C# CIL look very similar.
- I have put How the Compiler has declared all local variables in bold at the beginning of the method
- The VB Console wizard generates a Module, instead of a class
Why does VB generate a Module?
There has been a lot of discussion within the VB developer community whether modules are an Object Oriented Programming (OOP) approach. It is a common misconception that Modules are a carry over from VB6 and should therefore be avoided at all costs. Although a complete discussion on this hot topic of conversation is not in the scope of this article, I will point out that Modules in VB.NET are indeed object-oriented. You should think of a module as a special class type that cannot be used to create objects. It can contain only shared members; it cannot contain instance members. Although every member of a module is implicitly shared, you'll experience a compile-time error if you add the Shared keyword to any one.
Modules don't exist in the other languages so what makes VB so special?
One of the exciting new additions to the C# language in .NET 2.0 is the addition of static classes. Static classes and class members are used to create data and functions that can be accessed without creating an instance of the class. Static class members can be used to separate data and behavior that is independent of any object identity: the data and functions do not change regardless of what happens to the object. Static classes can be used when there is no data or behavior in the class that depends on object identity. So static classes can be thought of C#'s answer to the VB's humble module.
Whats wrong with the code?
Although this code at first glance looks fairly optimum and is functioning and the code can be compiled without their respective compilers throwing up any nasty errors. The code in essence not truly .NET-based code. If you were to run the code one would notice that the simple string concatenation routines can take anywhere between 10 - 44 ms (Milliseconds) to complete irrespective of what language you choose to run. Can this be optimized and what other problems can you see?
In Part 2 I will illustrate how even a simple application like this one can be optimized.
Sources
For some background information for my article I used some information obtained from Wikipedia on the .NET Framework.
History
- 06-10-06 Draft Version Part 1
- 09-10-06 Slightly updated to improve formatting and changed title
- 10-10-06 Updated with some more background information