Have you ever heard the term JIT? It’s an acronym for Just-In-Time. JIT compiler is a tool which performs the JIT compilation, which is a crucial feature of .NET applications. Let’s say a few more words on it today 🙂
What makes managed applications portable?
As we know from the previous posts published within the series, .NET Framework and CLR provide a lot of useful features for applications targeting the platform, such as automatic memory management. However, one of the main goals of managed runtimes’ invention was to make implemented applications portable.
So what does it mean than an application is portable? It means that, first of all, it can be run on any kind of hardware. Ideally, it should also be software-agnostic (especially OS-agnostic). We can still observe this trend for instance by the fact that Microsoft created ans is actively developing a multi-platform NET Core.
Such portability not only makes the application possible to be launched on any hardware or software platform, but also releases developers from taking care about underlying low-level structures. For instance when working with TPL, the programmer normally doesn’t need to change his/her code taking into account the underlying hardware (e.g. number or architecture of CPUs). It’s the same for memory allocation (described in the previous posts) where many close-to-metal details differ based on operating system’s architecture (32/64 bit) – CLR handles it for us.
However, at some point, every application needs to be executed by the processor, which requires having a machine code -assembly instructions understood and possible to be executed by the CPU. Depending on the OS’s or CPU’s architecture it’s sometimes necessary to use completely different CPU instructions sets in the assembly code. As you can guess, to make source code really portable it cannot be directly compiled to machine code. There’s something intermediate needed.
Intermediate Language (IL)
Because of the reasons described above, managed runtimes’ programming languages source code (like C#, F# or Java) is not directly compiled to assembly language. Instead, it’s firstly compiled to an intermediate language (IL). CLR’s intermediate language is also referred to as MSIL (Microsoft Intermediate Language).
Compilation of source code into IL is performed by the particular language’s compiler. This is the process which happens when you build your app by pressing F6 in Visual Studio or using csc.exe to compile your code.
Source code -> IL compilation is done by the particular language’s compiler. For instance, C# code is compiled by Roslyn. which is a C# language’s compiler:
Viewing MSIL
In order to see the MSIL code contained in the compiled EXE/DLL files (like I showed you e.g. here and here), you can use ILSpy Visual Studio extension, which, when installed, adds a menu option in Visual Studio (Tools -> ILSpy), where you can open any compiled file and view the IL code of contained objects:
What is actually compiled into IL code? For now we can say that the most important are methods (grouped into classes, namespaces etc. of course). There are also many other things compiled (even more than you would expect by looking at your source code), but we will focus on the methods for now.
OK, so we have the IL code now, but it cannot be understood by the CPU yet. How and when is it then compiled to assembly code?
Just-In-Time (JIT) compilation
How Toyota brought us JIT?
Let’s start by some historical background 🙂 In the early 1960s, Japanese engineers at Toyota had to reorganize their warehouse management, because they had very high storage costs – delivery of the parts from suppliers took a lot of time, because they ordered a lot in advance. Every delivery had to be handled by someone, so they needed a lot of employees. Ordered parts were stored in the warehouses for a long time, required a lot or storage space and maintenance.
In order to minimize the costs, they invented a just-in-time manufacturing (also known as Toyota Production System). Its main principle was to order goods from the suppliers only when the minimal level of stocks on the warehouse was reached. In effect, the goods were delivered just in time they were needed on the production line. It minimized the number of deliveries (and warehouse employees) and in the same time didn’t require that much storage space as before.
The whole idea can be imagined as the following flows order:
JIT compiler
Following Toyota’s success 😀 , CLR creators introduced a just-in-time (JIT) compiler into the framework. Its role is to compile intermediate language code into assembly language according to hardware and operating system’s characteristics (depending on the machine on which the code is being JIT-compiled). Unlike unmanaged languages, in which the source code is compiled into machine language prior to program’s execution, IL is (by default) compiled to CPU instructions at runtime. That’s how .NET engineers made the machine code delivered on time to the CPU.
JIT compiler is a part of CLR (similarly to garbage collector). To make things simple, it can be said that JIT compiles a particular method’s source code as soon as this method is called for the first time. Of course, it takes into account the hardware, e.g. the CPU type and its instructions set to produce correct assembly code. Such machine code is then saved in the cache and re-used every time the same method is called again within the same application’s execution. It means that the parts of the code which are not used (called) during the program’s execution may not be JIT-compiled at all.
Just-In-Time compilation is actually a combination of two approaches: ahead-of-time compilation and interpretation, mixing advantages and disadvantages of both. However, compiling IL code into assembly one at runtime allows for several interesting features like dynamic typing (ability to retrieve the actual object’s type at program’s execution). That’s why JIT compilation is often treated as a form of dynamic compilation.
Another advantage of having JIT compilation is that the source code we write is “further from the hardware”, so it can be made more readable and human-friendly, whereas the metal-touching stuff is “hidden” in the IL (kudos to MSIL developers!) 🙂
JIT compilation has also some cost – naturally it requires time to compile the code at runtime. On the other hand, various CPU units may have different powerful instructions sets or processing units that may be used in the assembly produced by the JIT, which wouldn’t be the case in ahead-of-time compilation (without any special tuning).
Can we still compile ahead-of-time?
If you really don’t like JIT (why would you?), there are ways to pre-JIT-compile your EXE/DLLs prior to an application’s execution. This is called Pre-JIT compilation. One of the reasons for that can be that a lot of users will run our application from the same EXE/DLL files, which would normally make JIT-compiling the IL for each user separately. In such case it may be relevant to pre-JIT-compile our application’s files for performance and memory usage reasons. It can be done using NGen (for all versions of .NET) or .NET Native (for .NET apps targeting Windows 10). We will not be describing these tools in details today (you can find more information on the Internet).
Summary
JIT compilation is a process of compiling an intermediate language (IL) into native (machine) code at runtime. It provides the portability of managed applications. It’s a very important concept to understand, as it’s introduced in a lot of currently used programming frameworks like Java, .NET and Android.
In one of the next posts we’ll dig into application execution model in .NET Framework, where we’ll see when and how exactly the JIT compilation process fits into the whole C#->CPU journey 🙂
Finally, as always, remember that:
so if you have your own opinion on JIT or any other remarks, don’t bother and share it in the comments! 😉