Buscando performance em .NET? Te ajudo a dar os primeiros passos para entender IL, JIT, WinDBG e Assembly

Que nível de otimizações podemos esperar do compilador do C# e do JIT?

Neste post, compartilho um pequeno, mas esclarecedor exemplo de como o(s) compilador(es) .NET nos ajuda a obter melhor performance. Além disso, fazemos um pequeno passeio por ferramentas e conceitos importantes para performance.

Em tempo, este exemplo é inspirado em outro encontrado no excelente (embora já um pouco datado) livro Writing high-performance .net applications.

Um pouco de contexto

Em .NET, o processo de compilação acontece em “dois momentos distintos”. Há dois compiladores – Roslyn (se usamos C# ou VB) e o JIT.

O primeiro “momento” de compilação ocorre quando geramos um executável a partir do código-fonte. Diferente do que ocorre com C++, por exemplo, nesse momento, não temos algo pronto (uma representação nativa pronta para ser executada pelo SO)! O que temos é uma representação binária do nosso código em uma “linguagem intermediária” (Intermediate Language ou IL).

O segundo “momento” ocorre quando executamos o programa. Nesse instante, o JIT (Just In Time compiler), converte o código IL para código nativo que o o SO e o computador conseguem executar.

Essa abordagem, aparentemente estranha, é, na verdade, uma grande vantagem. O JIT consegue “entender” o ambiente operacional onde o programa está sendo executado e gerar o melhor código nativo possível. Além disso, não precisamos nos preocupar com aspectos de compatibilidade quando estamos compilando nossa aplicação.

Nosso código de exemplo

Para ilustrar um pouco sobre esse processo, vamos trabalhar com um código muito simples. Aqui, fazemos duas chamadas para uma mesma função. Esta, por sua vez, retorna o resultado de uma adição com duas constantes.

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;
    }
}

O atributo MethodImplOptions.NoInlining é uma instrução para o JIT não realizar uma otimização extremamente comum. Veremos ela mais tarde.

O resultado em Intermediate Language

Como disse, o primeiro processo de compilação converte o código fonte em C# para uma linguagem intermediária.

Abaixo, vemos essa representação textual do resultado do processo de compilação. Essa representação foi gerada usando o ILSpy.

.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 pode ser entendida, na minha opinião, como a linguagem canonica de .NET. Independente da linguagem que utilizamos (C#, VB, F#…), uma representação em IL é gerada.

Podemos programar com IL diretamente, se desejarmos. Mas, o propósito dessa linguagem não é praticidade, e sim ajuste para .NET.

Repare, que a representação gerada pelo compilador já omite a operação de soma que fizemos no código original – já temos alguma otimização.

Depurando com WinDBG

Para vermos o JIT em ação utilizaremos uma ferramenta chamada WinDBG.

Trata-se da ferramenta de depuração padrão do Windows. Ela não serve para depurarmos apenas aplicações .NET, mas qualquer outro tipo de aplicação que seja compatível com a plataforma.

Obviamente, ela oferece bem mais poder do que temos no Visual Studio. Entretanto, esse poder a mais vem acompanhado de alguma complexidade. [tweet]Se você é desenvolvedor sério para Windows e precisa trabalhar com cenários pesados de depuração, precisa gastar algum tempo e aprender, pelo menos os fundamentos de WinDBG.[/tweet]

As instruções que seguem devem ser executadas logo após carregar o executável no WinDBG.

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

O que fazemos aqui foi fazer uma executar a aplicação até a carga do JIT e, depois, colocamos um breakpoint no início do método Main.

Aqui, temos um trecho código de Main já em Assembly.

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

As primeiras duas intruções call estão chamando nosso método TheAnswer. A terceira, está chamando o método Console.WriteLine.

Interessante objservar como todas as operações são executadas em registradores. O que faz com que a performance seja muito boa.

Depurando, linha por linha, a partir da primeira chamada do método TheAnswer vemos algo assim:

Entretanto, ao executarmos a segunda chamada, vamos algo assim:

O que aconteceu?

Em .NET, o JIT é acionado em toda “primeira execução” de um método. Ou seja, o JIT converte um método de IL para represetanção binária na primeira vez que o método é chamado fazendo todas as otimizações possíveis naquele instante.

O que ocorreu é que, na segunda execução do método já temos o método TheAnswer convertido para sua versão em Assembly.

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

Importante você perceber que 2Ah é 42 em hexa. O valor de retorno está em EAX.

Lição importante: A primeira (e apenas a primeira) execução de um método em .NET possui um overhead em função da execução do JIT.

Suportando Inlining

No início do post, mencionei que o atributo MethodImplOptions.NoInlining serve como instrução para o JIT não realizar uma otimização extremamente comum.

Inlining é uma otimização em que o JIT substitui chamadas para funções com “corpo de código” daquela função. Feita de forma pouco cuidadosa, essa operação pode impactar drasticamente no tamanho do executável gerado.

Vejamos o que ocorre com a depuração em WinDBG quando permitimos que o JIT utilize inlining com nosso código.

Eis o nosso novo Main.

mov  ecx, 54h
call mscorlib_ni+0xb4e31c
ret

O que o JIT fez? Ele percebeu que a função retorna uma constante. Logo, no lugar de chamar a função ele pode trazer a constante para o código. Então, também ficou evidente que não seria necessária uma soma. Assim, o JIT pegou apenas o valor 54h (84 em decimal) e chamou Console.WriteLine.

Isso é bem lindo!

Time to Action

Tanto a geração de IL quanto o JIT são processos extremamente poderosos que geram executáveis extremamente eficientes. Entender um pouco melhor sobre como esses dois mecanismos funcionam pode nos ajudar a explicar (e melhorar) o desempenho de nossas aplicações.

Recomendo que você gaste um pouco mais de tempo entendendo IL, aprendendo a usar o WinDBG e verificando o que está acontecendo “debaixo do capo”. Em muitos cenários, isso pode te ajudar a escrever código mais performático.

Continuarei tratando de performance tanto em português quanto em inglês. Assine a newsletter para receber notificações.

Capa: Alexander Hough

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:

Este post foi originalmente publicado em 2015 (infelizmente, a postagem original não está mais disponível) Nesse post vamos implementar, passo-a-passo,...
Na Guiando, a área de Implantação também está adotando Kanban (Não ficamos restritos ao desenvolvimento). Começando por lá, resolvemos adotar...
Neste post, compartilho um exemplo de implementação para um algoritmo extremamente famoso e importante: O algoritmo de Dijkstra. Trata-se de...
Nossos códigos precisam ser fáceis de compilar e testar. Para isso, nada melhor do que começarmos da forma certa, com...
Expressões regulares são fantásticas. Entretanto, precisam ser utilizadas com moderação pois podem impactar de forma perceptível a performance. A expressão...
In the previous post, you learned how to install RavenDB on your computer, create a database and load sample data....

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?