Trying to Improve Performance in .NET? Here are the basics you need to know about Intermediate Language, JIT, WinDBG, and Assembly

What kind of optimizations could we expect from the C# compiler and the JIT?

In this post, I would like to share a small, but helpful example of how the .NET compilers can help us to archive the best performance. Also, We will get in touch with some essential tools.

It is important to say that this example was inspired by the fantastic book keywords=.net+performance”>Writing high-performance .net applications.

The basics

When using .net, there are two different types of “compilation.” There are two compilers – Roslyn (if you are using C# and VB) and the JIT.

The first compilation occurs when we start the build process – from the source-code. It is different from what happens when using C++, for example. At this point, there is nothing “ready to run.”. The result of this process is a binary representation of our code expressed in an “Intermediate Language.”

The second compilation occurs when the program is executed. The JIT (Just In Time compiler), will generate the native code (from the IL) which is ready to run.

This approach is great. The JIT can generate the best native code as possible to the environment where your program needs to run. Also, it is easier to make it compatible.

Let’s write some code!

To make this process easier to understand, let start with a simple example. The following code calls a function two times. This function returns the result of adding two constants.

using System;
using System.Runtime.CompilerServices;

namespace UnderstandingJIT
{
    class Program
    {
        static void Main()
        {
            var a = TheAnswer();
            var b = TheAnswer();
            Console.WriteLine(a + b);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static int TheAnswer()
            => 21 + 21;
    }
}

The MethodImplOptions.NoInlining attribute instructs the JIT compiler to not perform an extremely common optimization. We will return to this later.

The resulting Intermediate Language

Like we know, the first compilation converts source code (in our case, C#), to an intermediate language.

The following code shows a text representation of this code. That representation was generated using the ILSpy tool.

.method private hidebysig static 
	void Main () cil managed 
{
	// Method begins at RVA 0x2050
	// Code size 19 (0x13)
	.maxstack 2
	.entrypoint
	.locals init (
		[0] int32 b
	)

	IL_0000: call int32 UnderstandingJIT.Program::TheAnswer()
	IL_0005: call int32 UnderstandingJIT.Program::TheAnswer()
	IL_000a: stloc.0
	IL_000b: ldloc.0
	IL_000c: add
	IL_000d: call void [mscorlib]System.Console::WriteLine(int32)
	IL_0012: ret
} // end of method Program::Main

.method private hidebysig static 
	int32 TheAnswer () cil managed 
{
	// Method begins at RVA 0x206f
	// Code size 3 (0x3)
	.maxstack 8

	IL_0000: ldc.i4.s 42
	IL_0002: ret
} // end of method Program::TheAnswer

IL is a canonical representation of dotNET. No matter what language you use (C#, VB or F#), in the end, we will always have IL.

We could use IL to write code directly. But, it is essential to understand that IL was not made to be practical. It is closer to the metal, not to the domain.

Inspecting the code, you can see that the compiler already did some optimizations. Right?

Debugging using WinDBG

Now, it is time to see the JIT compiler in action. To do that, let’s use WinDBG.

WinDBG is an excellent tool for Windows debugging (not limited to

WinDBG is powerful. Anyway, all this power is not free. Debugging with WinDBG is not easy when comparing with Visual Studio, for example. But, if you are a professional Windows Developer, and you need to deal with some complicated debugging scenarios, so you should spend some time learning at least the basics of how to use WinDBG.

Assuming that you have WinDBG on your computer, just load the executable generated by the Roslyn Compiler and then execute the following lines.

sxe ld clrjit
g
.loadby sos clr
!bpmd UnderstandingJIT.exe Program.Main
g

At this moment, we started running the application and paused it at the beginning of the Main method.

Here is the Main method in Assembly.

call dword ptr ds:[15B4D28h]
mov  esi, eax
call dword ptr ds:[15B4D28h]
add  esi, eax
mov  ecx, esi
call mscorlib_ni+0xbae32c

The first two call instructions are invoking the TheAnswer method. The third one is calling the Console.WriteLine method.

It is interesting to note that all the operations are using registers.

Executing one line at a time, when calling the TheAnswer we get something like that:

But, when running the second call, we get this:

It is interesting to note that all the operations are using registers.

Executing one line at a time, when calling the TheAnswer we get something like that.

What happened?
In .NET, the JIT compiler is executed in the “first execution” of each method. Whenever you run a method for the first time, this is the moment when the JIT will work on that method.

For the second time running the TheAnswer</em< method, there is no more compilation to do. So, we are ready to run.

push ebp
mov  ebp, esp
mov  eax, 2Ah
pop  ebp
ret

Note that 2Ah (hexadecimal) is 42 (decimal). The result value is stored on the EAX register.

To remember: The first time (and only the first time) running a method is when the JIT will convert it to native code (with a very little overhead).

Inlining

Previously, I said that the< code>MethodImplOptions.NoInlining prevents JIT to perform an important optimization. Let’s return to this topic.

Inlining is the name of a pretty ordinary optimization where the JIT replaces calls to functions with the code of that function. The resulting code is faster but bigger.

Let’s see what we get debugging the code with WinDBG after removing the code>MethodImplOptions.NoInlining attribute.

This is our new Main method.

mov  ecx, 54h
call mscorlib_ni+0xb4e31c
ret

What happened? JIT knows that TheAnswer returns a constant. So, instead of doing an expensive operation (calling a method), JIT just used the result. The next logical step was to do the add in the compilation time. Now, all our code do is call Console.WriteLine method passing 54h (84 decimal).

Beautiful.

Compartilhe este insight:

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

Não são raros os casos onde a distância entre um “desejo” e a “realização” é a “tentativa”. Ou seja, partindo...
As a consultant, I often need to work with the code that I do not know. I need to understand...
As lojas que podem e que insistem em funcionar, na minha cidade, estão limitando o número de clientes atendidos simultaneamente....
I’ve been spending some time learning from the “Designing Data-Intensive Applications” book. I am reading it for the third time,...
Some days ago, I heard a fantastic interview with Phil Haack on the IT Career Energizer Podcast. Here is the...
Nesse ano, palestrei na APIX sobre microsserviços. Abaixo, registro em vídeo feito pela organização do evento. Comentários? Feedback?

Curso Reputação e Marketing Pessoal

Masterclasses

01

Introdução do curso

02

Por que sua “reputação” é importante?

03

Como você se apresenta?

04

Como você apresenta suas ideias?

05

Como usar Storytelling?

06

Você tem uma dor? Eu tenho o alívio!

07

Escrita efetiva para não escritores

08

Como aumentar (e manter) sua audiência?

09

Gatilhos! Gatilhos!

10

Triple Threat: Domine Produto, Embalagem e Distribuição

11

Estratégias Vencedoras: Desbloqueie o Poder da Teoria dos Jogos

12

Análise SWOT de sua marca pessoal

13

Soterrado por informações? Aprenda a fazer gestão do conhecimento pessoal, do jeito certo

14

Vendo além do óbvio com a Pentad de Burkle

15

Construindo Reputação através de Métricas: A Arte de Alinhar Expectativas com Lag e Lead Measures

16

A Tríade da Liderança: Navegando entre Líder, Liderado e Contexto no Mundo do Marketing Pessoal

17

Análise PESTEL para Marketing Pessoal

18

Canvas de Proposta de Valor para Marca Pessoal

19

Método OKR para Objetivos Pessoais

20

Análise de Competências de Gallup

21

Feedback 360 Graus para Autoavaliação

22

Modelo de Cinco Forças de Porter

23

Estratégia Blue Ocean para Diferenciação Pessoal

24

Análise de Tendências para Previsão de Mercado

25

Design Thinking para Inovação Pessoal

26

Metodologia Agile para Desenvolvimento Pessoal

27

Análise de Redes Sociais para Ampliar Conexões

Lições complementares

28

Apresentando-se do Jeito Certo

29

O mercado remunera raridade? Como evidenciar a sua?

30

O que pode estar te impedindo de ter sucesso

Recomendações de Leituras

31

Aprendendo a qualificar sua reputação do jeito certo

32

Quem é você?

33

Qual a sua “IDEIA”?

34

StoryTelling

35

Você tem uma dor? Eu tenho o alívio!

36

Escrita efetiva para não escritores

37

Gatilhos!

38

Triple Threat: Domine Produto, Embalagem e Distribuição

39

Estratégias Vencedoras: Desbloqueie o Poder da Teoria do Jogos

40

Análise SWOT de sua marca pessoal

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?