Esse é a primeira parte de três posts sobre as ferramentas dentro do “Performance Profiler” do Visual Studio! 🙂
Provavelmente você deve está pensando… “Mais um artigo explicando ferramentas interessantes, mas para usá-las vou ter que pagar por uma licença do Visual Studio“… bom, desta vez você está errado! Você consegue utilizar todos os recursos que vou apresentar aqui, usando o Visual Studio Community. 😉
Acredito que você vai curtir o que elas podem fazer!
Para que serve as ferramentas do Performance Profiler?
Esse conjunto de ferramentas dentro do Performance Profiler serve para auxiliar a investigação de problemas de desempenho (performance) dentro da sua aplicação. Você consegue utilizá-las através do Visual Studio em todas as versões, incluindo o Visual Studio Community (versão gratuita do Visual Studio, link).
Com esse conjunto de ferramentas nós conseguimos analisar e investigar:
- Problemas de memória: é possível identificar se existe vazamento de memória (memory leaks), isso ocorre quando o valor da memória da sua aplicação dentro do Gerenciador de Tarefas aumenta indefinidamente e nunca se estabiliza, ou consumo excessivo de memória em pequenos processos, entre outros. Além disso, ela é ótima para analisar códigos que podem necessitar de otimização;
- Tempo de consumo da CPU: através desta ferramenta conseguirmos avaliar o uso de CPU e quanto tempo está sendo gasto dentro de um processo ou execução de uma aplicação;
- Rastrear alocação de objetos: é possível investigar onde os objetos são alocados e quando eles são reciclados pelos Coletor de Lixo (Garbage Collection – GC);
- Avaliar determinadas chamadas e os seus tempos: você consegue instrumentar sua aplicação para investigar uma determinada chamada de função/ método e o tempo desta chamada;
- Avaliar os indicadores .Net (.Net Counters): dentro do ecossistema do .NET existem alguns indicadores que informam sobre o tamanho da memória Heap e quando foi necessário usá-la para executar aquele código, ou o tempo médio de execução do GC, entre outros; e são úteis para obter um overview de como está a sua aplicação.
- Events viewers: nós conseguimos avaliar os logs e mensagens geradas pela aplicação, ou pelo IIS (Internet Information Services) ou gerado pelo próprio sistema operacional, relacionado a execução da sua aplicação;
- Problemas dentro de códigos assíncronos: é possível identificar se existem problemas de concorrência entre threads, ou mal uso dos recursos de async/await;
- Tempo de execução de queries na base de dados: também é possível avaliar o tempo de execução das queries executadas na base de dados.
- Timeline de execução de aplicações XAML: através desta ferramenta é possível avaliar o tempo que a sua aplicação em XAML demora para renderizar todos os componentes.
Para cada funcionalidade que mencionei acima, abaixo estão as respectivas ferramentas:
- Memory Usage;
- CPU Usage;
- .NET Object Allocation Tracking;
- Instrumentation;
- .NET Counters;
- Events Viewer;
- .NET Async;
- Database;
- Application Timeline.
O painel do Performance Profiler com todas as ferramentas é esse:
NOTA: Talvez você esteja se perguntando sobre a Diagnostic Tools do Visual Studio (imagem abaixo). Dentro do Diagnostic Tools têm versões mais simples das ferramentas: Memory Usage, CPU Usage e Events Viewer. Além disso, elas só são executadas em modo Debug (executando todo o código e colocando break points). Então, resolvi focar este post para apresentar as versões mais completas destas ferramentas e das outras, através do Performance Profiler.
Performance Profiler: ferramentas para analisar o desempenho de Aplicações
Nestes três posts, vou apresentar para vocês os recursos das seguintes ferramentas:
- Memory Usage;
- CPU Usage;
- .NET Object Allocation Tracking;
- .NET Counters;
- .NET Async;
- Database.
As demais ferramentas (Instrumentation, Events Viewer e Application Timeline) serão apresentadas em outro post no futuro. Neste primeiro momento vou focar nestas 6 ferramentas por serem voltadas para análise de desempenho. E já vou adiantar que você vai se impressionar com elas! Vamos conseguir entrar no bit da execução do código (literalmente).
Acesando: Performance Profiler
Para acessar o Performance Profiler no Visual Studio, basta pressionar Alt+F2 ou ir através do menu desta maneira:
- Debug > Performance Profiler:
NOTA: se você estiver abrindo essa ferramenta em modo Debug, vai aparecer a mensagem abaixo. Essa mensagem informa que seria melhor alterar a solução para executar em modo de Release, assim resultados vão ser mais precisos. Isso é porque, quando nós executamos a aplicação em modo Debug existem instrumentos e outras ferramentas dentro do Visual Studio que ficam analisando a aplicação, sendo assim o resultado vai sofrer alguma interferência.
Todo o código fonte apresentados nos exemplos deste post estão dentro do meu GitHub: https://github.com/MackMendes/Evaluation-Performance-Tools.
Ferramenta: Memory Usage
Como mencionado acima, a Memory Usage é uma excelente ferramenta para investigar possíveis problemas de memória na sua aplicação, incluindo perda de memória (memory leaks).
Vou iniciar executando a ferramenta, apresentar para vocês a maneira de manuseá-la e as funcionalidades dela. Depois vou falar um pouco sobre as configurações que ela possui para obter informações da utilização da memória do código Gerenciado e/ou do Nativo.
Primeiro, vamos selecionar essa ferramenta no painel da Performance Profiler e depois execute ela:
Logo quando você iniciar a execução da ferramenta, vai aparecer a seguinte tela:
Nesta tela você tem as seguintes opções:
- Neste gráfico, nós temos a informação do consumo de memória em MB pelo de análise da ferramenta. Cada vez que o coletor de lixo (Garbage Collection – GC) for acionado será indicado neste gráfico, assim como mensagens de log usando a API do LoggingChannel (User mask), eventos produzidos pela aplicação (App lifecycle mark) ou quando mais de um dos três indicadores anteriores são realizados ao mesmo tempo (Merged mark). Além disso, você também consegue selecionar um período de tempo no gráfico para verificar melhor quanto tempo aquele período demorou e qual foi o total de memória utilizada;
- Force GC: Esse botão é para forçar que o GC seja executado e colete todo o lixo das gerações (G0, G1 e G2). Na visão de experimentação, é interessante poder acionar o GC a qualquer momento, pois você consegue identificar o comportamento da aplicação quando o GC é acionado. Contudo, é extremamente recomentado não forçar a executação do GC em código de Produção! A Microsoft vem melhorando muito a forma com que o GC trabalha e temos diferenças grandes de performance dentro do .Net Core, muito por causa da forma que o GC vem sendo melhorado (link);
- Take snapshot: Essa ferramenta irá coletar automaticamente informações relacionadas aos marcadores que estão no gráfico (item 1). Contudo, para obter informações mais detalhadas sobre do que está sendo executado em um determinado momento e qual é o consumo exato de memória, é necessário acionar esse botão. Cada vez que você aciona ele, a ferramenta vai tirar uma foto do consumo de memória e vários detalhes exatamente naquele instante, depois volta continua executando sem coletar essas informações detalhadas. Então, quando você estiver executando essa ferramenta, para ter mais detalhes, você vai precisar tirar algumas fotos. E cada foto que você tirar vai ter a comparação com a foto antecessora, sendo a primeira foto a baseline. Logo abaixo eu explico com detalhes.
Depois de ter tirado todos snapshots necessárias, e ter a certeza que passou pelos trechos de códigos que você gostaria de avaliar, pode parar a execução da aplicação. Ao parar, você vai ver a seguinte tela:
Na parte do gráfico têm indicadores mostrando quando e quantas vezes o GC foi acionado, o tempo da execução e a quantidade de memória utilizada nesta execução, conforme é possível vê abaixo:
Se você parar o mouse em cima dos marcadores no gráfico, você vai ver mais detalhes, por exemplo no caso abaixo, onde você tem informações detalhes de quantas vezes o GC foi acionado neste momento (19 vezes), qual foi a razão de ser acionado (pequeno objeto alocado na memória HEAP) e em qual secundo ele foi acionado:
Cada snapshot que você tirou vai mostrar informações comparadas com o snapshot anterior. Por exemplo neste caso o Snapshot #1 é o baseline, mas o Snapshot #2 vai mostrar a diferença entre ele e o #1 com relação ao consumo de memória, sendo +16,67 MB maior (comparado com o Snapshot #1). No caso do Snapshot #3, vai ser a comparação dele com o #2 e assim por diante.
Se você estiver analisando uma aplicação com interface gráfica (UI), onde está indicando “screenshot is not available” no exemplo acima, estará mostrando um printscreen da tela no momento que tirou o snapshot.
E por último, mas não menos importante, nós conseguimos entrar no detalhe qual é o tipo do objeto que mais consumiu memória nesta análise. Isso é muito interessante para investigar melhor o problema que esteja ocorrendo na sua aplicação.
Para isso, você só precisa clicar no indicador dentro do snapshot abaixo:
E assim, vai abrir a seguinte tela:
Assim, como você consegue ver, têm todos os tipos de objetos que mais consumiram recursos de memória neste snapshot. Meu conselho é que você escolha observar o snapshot que tiver a indicação de maior aumento de consumo entre os snapshots, assim como ocorreu no Snapshot #2 acima.
Então, para você ter uma ideia, neste exemplo que executei essa análise é em um código que estou lendo um arquivo csv, carregando tudo a memória para calcular uma média de valores dentro dos dados deste csv. O código é esse (source):
Program
{
static void Main(string[] args)
{
Console.WriteLine("================ Starting work ================");
CalculateMovieRatings.Version1();
Console.WriteLine("================ Finished! ================");
}
}
// CalculateMovieRatings.cs
public static void Version1()
{
var lines = File.ReadAllLines(this.filePath);
var sum = 0d;
var count = 0;
foreach (var line in lines)
{
var parts = line.Split(',');
if (parts[1] == "223")
{
sum += double.Parse(parts[2], CultureInfo.InvariantCulture);
count++;
}
}
Console.WriteLine($"The average is {sum / count}.");
}
O problema está em trazer tudo para a memória. Por isso que na lista dos tipos de objetos que mais consomem memória, ficou em primeiro um Dicionário de String e Objeto e no segundo lugar ficou uma String com mais de 10 MB:
Nesta caso, eu teria que pensar em uma abordagem melhor, não trazendo tudo para a memória, mas sim fazendo a avaliação durante a leitura do arquivo.
Vou deixar o link da documentação desta ferramenta para você lê mais sobre o assunto: https://docs.microsoft.com/en-us/visualstudio/profiling/memory-usage-without-debugging2?view=vs-2019
Ferramenta: CPU Usage
A CPU Usage é a ferramenta perfeita para você descobrir qual é o método ou a linha de código que está afetando diretamente o desempenho da sua aplicação em relação a processamento de CPU.
Vamos começar selecionando-a no painel da Performance Profiler e iniciar a avaliação:
Depois que a aplicação rodar, a ferramenta vai mostrar um resultado parecido com o que está na imagem abaixo. Caso a sua aplicação seja Web API ou de algum outro tipo que fique executando constantemente, basta você parar de executar a aplicação assim que você tiver certeza que passou pelo trecho de código que gostaria de avaliar.
Basicamente, o resultado da ferramenta é um dashboard interativo, com as seguintes informações:
- Um gráfico com o percentual de CPU utilizado com base no tempo de execução da sua aplicação. Através dele você já consegue ter um overview de possíveis oscilações no consumo comum de CPU, por exemplo apresentação de spikes ou de vales no gráfico. Além disso, você também consegue selecionar um período de tempo no gráfico e todas as outras informações neste dashboard (2, 3 e 4) vão ser ajustadas conforme o período que você selecionou. Na gif logo abaixo você consegue vê isso;
- Top Functions: dentro desta parte vai conter os métodos que exigiram mais da CPU, sendo possíveis ofensores de performance dentro da aplicação;
- Hot Path: você vai ver o caminho dos métodos que mais consumiram CPU. A visão aqui é hierárquica com base no método base (o método pai);
- Um gráfico em formato pizza, mostrando as categorias dos métodos: Entrada/Saída de dados (I.O.), do Framework .Net (Kernel), Sistema de arquivo (File system), Redes (Networking) ou outros; que mais consumiram CPU.
Esse é o overview geral do resultado da ferramenta CPU Usage. Além disso, nós conseguimos seguir a trilha das informações que são apresentadas neste dashboard para chegar até a linha de código ofensora de desempenho de CPU dentro da nossa aplicação. Para mostrar isso, é mais fácil analisar um caso, por isso abaixo vou entrar nestes detalhes.
Exemplo prático
Para continuar apresentando a ferramenta CPU Usage, vou analisar uma aplicação simples. Essa aplicação é uma Console Application que lê um arquivo csv (com aproximadamente 450 MB) e calcula a média de um determinado conjunto de dados. O código é o mesmo usado no exemplo acima: CalculateMovieRatings: Version1.
Contudo, como vocês podem ter repado no código, é que estamos lendo tudo que está no arquivo e trazemos para a memória. Já sabemos que isso não é bom no aspecto de memória, mas vamos conseguir identificar pela ferramenta quais são os pontos deste código que mais afetam a CPU também.
Após executar a ferramenta vou analisar toda a execução, porque é uma aplicação muito simples, se analisarmos apenas alguns trechos da execução pode haver indicação de problemas de desempenho com a utilização do .Net Framework (Kernel) e não é o caso, como vocês podem vê abaixo:
Pelos resultados, já conseguimos identificar através do Top Functions que o grande ofensor é um código externo (59,11%), seguido pela sua chamada (27,84%), normalmente são métodos de pacotes ou bibliotecas externas:
Nos casos que indicam código externo, temos que verificar o método dentro da nossa aplicação que chama ele. Assim, analisaremos com calma o código e podemos alterar alguma coisa para melhorar a nossa aplicação. Então, se olharmos para o Hot Path já conseguirmos identificar o nosso método: Presentation.ConsoleApplication.EvaluationVisualStudioTool.Benchmarkers.CalculateMovieRatings.Version1():
Sendo assim, vamos clicar no nome completo do nosso método, mostrado na imagem acima. Logo em seguida, vai abrir um outro dashboard contendo essas informações:
Clique com o botão direito em cima da linha indicada e selecione a opção Load Module Symbols:
E assim, você vai conseguir visualizar as linhas de códigos que são os ofensores dentro da nossa aplicação, e consequentemente estão acionando esse método externo. Para isso, basta você clicar 2x em cima da mesma linha que você clicou acima, assim a ferramenta vai nos mostrar:
Podemos identificar que o problema está na linha de código onde pegamos todo o conteúdo que está no arquivo .csv e jogamos na memória. O segundo ponto é quando estamos percorrendo cada linha deste objeto com todo o conteúdo do arquivo na memória, para calcular a média. O código foi identificado! 🙂
Agora a solução seria fazer uma abordagem onde lemos linha a linha do arquivo, sem trazer tudo para a memória. De qualquer forma, o mais importante foi que identificamos exatamente onde estava o problema! Muito legal, não?!
Essa ferramenta atualmente tem poucas configurações, basicamente nós conseguimos alterar a quantidade de vezes que a ferramenta analisa a aplicação. Para acessar as configurações basta entrar na engrenagem do lado da ferramenta no painel no Performance Profile:
Ao abrir as configurações dela, você vai vê essa tela:
Eu particularmente, uso as configurações default e não senti a necessidade de alterá-las. Contudo, dependendo do cenário, você pode vim aqui e mudá-las.
Deixo aqui o link da documentação desta ferramenta: https://docs.microsoft.com/en-us/visualstudio/profiling/cpu-usage?view=vs-2019 (está desatualizada quando escrevi esse post, mas em breve vão atualizar). E vou deixar também um vídeo que a equipa da Microsoft fala sobre ela. É muito interessante!
Continuação
Nos próximos posts vou apresentar as ferramentas:
- .NET Object Allocation Tracking;
- .NET Counters;
- .NET Async;
- Database;
Então, até o próximo post.
Referências
Vídeos
- Performance Profiling | Part 1 An Introduction
- Performance Profiling | Part 2: Choosing the right tool
- Performance with Profiling Part 3: Profiling and Production
Artigos
- Doc about .NET Memory Performance Analysis: https://github.com/Maoni0/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.md
- Memory management and garbage collection (GC) in ASP.NET Core: https://docs.microsoft.com/en-us/aspnet/core/performance/memory?view=aspnetcore-5.0
- Performance Diagnostic Tools: https://docs.microsoft.com/en-us/aspnet/core/performance/diagnostic-tools?view=aspnetcore-3.0
- Measure app performance in Visual Studio: https://docs.microsoft.com/en-us/visualstudio/profiling/?view=vs-2019
- Profile on HPC (high performance computing) clusters: https://docs.microsoft.com/en-us/visualstudio/profiling/profiling-on-hpc-high-performance-computing-clusters?view=vs-2017
- Getting started with performance tools: https://docs.microsoft.com/en-us/visualstudio/profiling/getting-started-with-performance-tools?view=vs-2017
- First look at profiling tools: https://docs.microsoft.com/en-us/visualstudio/profiling/profiling-feature-tour?view=vs-2019
- Run profiling tools with or without the debugger: https://docs.microsoft.com/en-us/visualstudio/profiling/running-profiling-tools-with-or-without-the-debugger?view=vs-2019
- PerfTips: https://docs.microsoft.com/en-us/visualstudio/profiling/perftips?view=vs-2019
- What diagnostic tools are available in .NET Core?: https://docs.microsoft.com/en-us/dotnet/core/diagnostics/
- Tutorial: Measure performance using EventCounters in .NET Core: https://docs.microsoft.com/en-us/dotnet/core/diagnostics/event-counter-perf
- Trace .NET applications with PerfCollect: https://docs.microsoft.com/en-us/dotnet/core/diagnostics/trace-perfcollect-lttng
- Fundamentals of garbage collection: https://docs.microsoft.com/pt-br/dotnet/standard/garbage-collection/fundamentals
- Measure memory usage in Visual Studio: https://docs.microsoft.com/en-us/visualstudio/profiling/memory-usage?view=vs-2019
- STACK, HEAP, GARBAGE COLLECTOR, PERFORMANCE & ARQUITETURA DE SOFTWARE: https://www.eximiaco.tech/pt/2020/06/28/stack-heap-garbage-collector-performance-arquitetura-de-software/
- PARSING LARGE FILES: https://www.eximiaco.tech/en/2019/06/11/parsing-large-files/