Modern software programming languages (like C# and VB.NET) utilize a human-friendly syntax that is not directly understandable by computers. Software commands in this human-friendly syntax are referred to as Source Code. Before a computer can execute the source code, special programs called compilers must rewrite it into machine instructions, also known as object code. This process (commonly referred to simply as “compilation”) can be done explicitly or implicitly.
Explicit compilation converts the upper level language into object code prior to program execution. Ahead of time (AOT) compilers are designed to ensure that, the CPU can understand every line in the code before any interaction takes place.
Implicit compilation is a two-step process. The first step is converting the source code to intermediate language (IL) by a language-specific compiler. The second step is converting the IL to machine instructions. The main difference with the explicit compilers is that only executed fragments of IL code are compiled into machine instructions, at runtime. The .NET framework calls this compiler the JIT (Just-In-Time) compiler.
Delivering portability is a key aspect when developing a program targeting a wide range of platforms. A couple of questions need answers to enable execution on multiple platforms:
- What kind of CPU is used?
- What Operating System (OS) will the program be running on?
To enable maximum reach of the software, the source code has to be compiled with a wide range of explicit compilers.
The implicit way delivers portability quite more effortlessly, because the first step of the process is much more platform agnostic. Each target platform has a JIT compiler deployed and as long as the IL can be interpreted the program can execute. The initial compiler does not need to know all of the places where the software might run.
The JIT compiler is part of the Common Language Runtime (CLR). The CLR manages the execution of all .NET applications. In addition to JIT compilation at runtime, the CLR is also responsible for garbage collection, type safety and for exception handling.
Different machine configurations use different machine level instructions. As Figure 1 shows, the source code is compiled to exe or dll by the .NET compiler. Common Intermediate Language (CIL) consists of instructions that any environment supporting .NET can execute and includes metadata describing structures of both data and code. The JIT Compiler processes the CIL instructions into machine code specific for an environment. Program portability is ensured by utilizing CIL instructions in the source code. The JIT compiler compiles only those methods called at runtime. It also keeps track of any variable or parameter passed through methods and enforces type-safety in the runtime environment of the .NET Framework.
There are three types of JIT compilation in the .NET framework:
Normal JIT Compilation
With the Normal JIT Compiler (figure 2) methods are compiled when called at runtime. After execution this method is stored in the memory and it is commonly referred as “jitted”. No further compilation is required for the same method. Subsequent method calls are accessible directly from the memory cache.
Econo JIT Compilation
The Econo JIT Compiler is displayed in figure 3. It compiles methods when called at runtime and removes them from memory after execution.
Another form of compilation in .NET is called Pre-JIT compilation. It compiles the entire assembly instead of used methods. In .NET languages, this is implemented in Ngen.exe (Native Image Generator). All CIL instructions are compiled to native code before startup, as shown in figure 4. This way the runtime can use native images from the cache instead of invoking the JIT Compiler.
Pros and Cons
Both implicit and explicit compilations have advantages and disadvantages.
- Ahead of time (AOT) delivers faster start-up time, especially in large applications where much code executes on startup. But it requires more disk space and more memory/virtual address space to keep both the IL and precompiled images. In this case the JIT Compiler has to do a lot of disk I/O actions, which are quite expensive.
- JIT can generate faster code, because it targets the current platform of execution. AOT compilation must target the lowest common denominator among all possible execution platforms.
- JIT can profile the application while it runs, and dynamically re-compile the code to deliver better performance in the hot path (the most used functions).
JustTrace and Compilation
JustTrace’s performance profiler uses the JIT Compiler in .NET to collect data. The tracing profiler tracks every time the runtime enters or leaves methods. It also shows method hit counts, own time and total time spent by methods. figure 5 shows the All Methods view which gives information on how much time is spent by different methods.
JustMock and Compilation
On the other hand, JustMock instruments static and non-virtual functions before they are jitted. JustMock replaces the desired method with its mocked instance and its behavior at runtime. This achieves a great degree of flexibility.
The following test method shows how the mocked instance of Foo swaps places with the original one. In the Arrange part of the test a mocked instance is created and the method Echo(1) is arranged to return 2. In the Act the original method is called. In the Assert part we ensure that foo.Echo called with argument 1 really returns 2 and that it is called only once.
The JIT Compiler resolves most of the limitations of the explicit compilation and delivers additional functional benefits. Programs in .NET are available on a wide range of platforms. The .NET JIT Compiler can optimize the most used IL code for even faster performance. Data can be collected by and imported to the JIT Compiler.