Compartilhar via


Estudo de Caso: Guia do Iniciante para otimizar o código e reduzir os custos de computação (C#, Visual Basic, C++, F#)

Otimizar seu código reduz o tempo e os custos de computação. Este estudo de caso demonstra como usar ferramentas de criação de perfil do Visual Studio para identificar e corrigir problemas de desempenho em um aplicativo .NET de exemplo. Se você quiser comparar as ferramentas de criação de perfil, consulte Qual ferramenta devo escolher?

Este guia aborda:

  • Como usar as ferramentas de criação de perfil do Visual Studio para analisar e melhorar o desempenho.
  • Estratégias práticas para otimizar o uso da CPU, a alocação de memória e as interações de banco de dados.

Aplique essas técnicas para tornar seus próprios aplicativos mais eficientes.

Estudo de caso de otimização

O aplicativo .NET de exemplo executa consultas em um banco de dados SQLite de blogs e postagens usando o Entity Framework. Ele executa muitas consultas, simulando um cenário de recuperação de dados do mundo real. O aplicativo se baseia no exemplo de introdução do Entity Framework, mas usa um conjunto de dados maior.

Os principais problemas de desempenho incluem:

  • Alto uso da CPU: cálculos ineficientes ou tarefas de processamento aumentam o consumo e os custos da CPU.
  • Alocação de memória ineficiente: o gerenciamento de memória ruim leva à coleta de lixo excessiva e à redução do desempenho.
  • Sobrecargas de banco de dados: consultas ineficientes e chamadas excessivas de banco de dados prejudicam o desempenho.

Este estudo de caso usa ferramentas de criação de perfil do Visual Studio para identificar e resolver esses problemas, com o objetivo de tornar o aplicativo mais eficiente e econômico.

Desafio

Corrigir esses problemas de desempenho envolve vários desafios:

  • Diagnosticar gargalos: identificar as causas raiz de altas sobrecargas de CPU, memória ou banco de dados requer o uso efetivo de ferramentas de criação de perfil e uma interpretação correta dos resultados.
  • Restrições de conhecimento e recursos: a criação de perfil e a otimização exigem habilidades e experiência específicas, que podem nem sempre estar disponíveis.

Uma abordagem estratégica que combina ferramentas de criação de perfil, conhecimento técnico e testes cuidadosos é essencial para superar esses desafios.

Estratégia

Aqui está uma visão de alto nível da abordagem neste estudo de caso:

A coleta de dados requer as seguintes tarefas:

  • Defina o aplicativo como versão de compilação.
  • Selecione a ferramenta Uso da CPU no Criador de Perfil de Desempenho (Alt+F2).
  • No Analisador de Desempenho, inicie o aplicativo e colete um traço.

Inspecionar áreas de alto uso da CPU

Depois de coletar um rastreamento com a ferramenta Uso da CPU e carregá-lo no Visual Studio, primeiro verificamos a página de relatório inicial .diagsession que mostra os dados resumidos. Use o link Detalhes abertos no relatório.

Captura de tela dos detalhes de abertura na ferramenta de uso da CPU.

Na exibição de detalhes do relatório, abra a exibição Árvore de Chamadas. O caminho do código com maior uso de CPU no aplicativo é chamado de caminho frequente. O ícone de chama de caminho frequente (Captura de tela que mostra o ícone do Caminho Frequente.) pode ajudar a identificar rapidamente problemas de desempenho que podem ser melhorados.

No modo de exibição Árvore de Chamadas, você pode ver o alto uso da CPU para o método GetBlogTitleX no aplicativo, usando cerca de 60% de participação no uso da CPU do aplicativo. No entanto, o valor de CPU própria para GetBlogTitleX é baixo, apenas cerca de 0,10%. Ao contrário do valor de CPU total, o valor de CPU Própria exclui o tempo gasto em outras funções, portanto, devemos procurar pelo o gargalo real mais para baixo na árvore de chamadas.

Captura de tela da visualização da árvore de chamadas na ferramenta de Uso da CPU.

GetBlogTitleX faz chamadas externas para duas DLLs LINQ, que estão usando a maior parte do tempo de CPU, conforme evidenciado pelos valores muito altos de CPU própria. Essa é a primeira pista de que uma consulta LINQ pode ser uma área a ser otimizada.

Captura de tela da exibição Árvore de Chamadas na ferramenta Uso da CPU com a CPU Própria em destaque.

Para obter uma árvore de chamadas visualizada e uma exibição diferente dos dados, abra a exibição Flame Graph. (Ou clique com o botão direito do mouse em GetBlogTitleX e escolha Visualizar no Gráfico de Chamas.) Aqui novamente, parece que o método GetBlogTitleX é responsável por grande parte do uso da CPU do aplicativo (mostrado em amarelo). As chamadas externas para as DLLs do LINQ aparecem abaixo da caixa GetBlogTitleX e estão usando todo o tempo de CPU para o método.

Captura de tela da exibição do Gráfico de Chamas na ferramenta de uso da CPU.

Coletar dados adicionais

Muitas vezes, outras ferramentas podem fornecer informações adicionais para ajudar a análise e isolar o problema. Neste estudo de caso, adotaremos a seguinte abordagem:

  • Primeiro, examine o uso da memória. Pode haver uma correlação entre o alto uso da CPU e o alto uso de memória, portanto, pode ser útil examinar ambos para isolar o problema.
  • Como identificamos as DLLs LINQ, também examinaremos a ferramenta de Banco de Dados.

Verificar o uso da memória

Para ver o que está acontecendo com o aplicativo em termos de uso de memória, coletamos um rastreamento usando a ferramenta Alocação de Objetos .NET (no C++, você pode usar a ferramenta Uso de Memória). O modo de exibição Árvore de Chamadas no rastreamento de memória mostra o caminho crítico e nos ajuda a identificar uma área de alto uso de memória. Não é surpresa, neste momento, o método GetBlogTitleX parece estar gerando muitos objetos! Mais de 900.000 alocações de objeto, na verdade.

Captura de tela da exibição da Árvore de Chamadas na ferramenta de Alocação de Objeto .NET.

A maioria dos objetos criados são cadeias de caracteres, matrizes de objetos e Int32s. Podemos ver como esses tipos são gerados examinando o código-fonte.

Verificar a consulta na ferramenta Banco de Dados

No Criador de Perfil de Desempenho, selecionamos a ferramenta Banco de Dados em vez de Uso da CPU (ou, selecione ambas). Depois de coletar um rastreamento, abra a guia Consultas na página de diagnóstico. Na guia Consultas para o rastreamento de banco de dados, você pode ver que a primeira linha mostra a consulta mais longa, 2446 ms. A coluna Registros mostra quantos registros a consulta lê. Você pode usar essas informações para comparação posterior.

Captura de tela das consultas de banco de dados na ferramenta Banco de Dados.

Examinando a instrução SELECT gerada pelo LINQ na coluna Consulta, identificamos a primeira linha como a consulta associada ao método GetBlogTitleX. Para exibir a cadeia de caracteres de consulta completa, expanda a largura da coluna. A cadeia de caracteres de consulta completa é:

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

Observe que o aplicativo está recuperando muitos valores de coluna aqui, talvez mais do que precisamos. Vamos examinar o código-fonte.

Otimizar código

É hora de dar uma olhada no código-fonte GetBlogTitleX. Na ferramenta Banco de Dados, clique com o botão direito do mouse na consulta e escolha Ir para o Arquivo de Origem. No código-fonte para GetBlogTitleX, encontramos o código a seguir que usa LINQ para ler o banco de dados.

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

Esse código usa foreach loops para pesquisar no banco de dados qualquer blog com "Fred Smith" como autor. Olhando para ele, você pode ver que muitos objetos estão sendo gerados na memória: uma nova matriz de objetos para cada blog no banco de dados, cadeias de caracteres associadas para cada URL e valores para propriedades contidas nas postagens, como ID do blog.

Fazemos uma pequena pesquisa e encontramos algumas recomendações comuns sobre como otimizar consultas LINQ. Como alternativa, podemos economizar tempo e deixar Copilot fazer a pesquisa por nós.

Se estivermos usando o Copilot, selecionamos Perguntar ao Copilot no menu de contexto e digitar a seguinte pergunta:

Can you make the LINQ query in this method faster?

Dica

Você pode usar comandos de barra, como /optimize, para ajudar a formular boas perguntas para o Copilot.

Neste exemplo, Copilot fornece as seguintes alterações de código sugeridas, juntamente com uma explicação.

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

Esse código inclui várias alterações para ajudar a otimizar a consulta:

  • A cláusula Where foi adicionada e um dos loops foreach foi eliminado.
  • Projetamos apenas a propriedade Title na instrução Select, que é tudo que precisamos neste exemplo.

Em seguida, refazemos o teste usando as ferramentas de diagnóstico.

Resultados

Depois de atualizar o código, executamos novamente a ferramenta Uso da CPU para coletar um rastreamento. O modo de exibição de Árvore de Chamadas mostra que GetBlogTitleX está executando apenas 1754 ms, usando 37% do total de CPU do aplicativo, um aprimoramento significativo de 59%.

Captura de tela do uso aprimorado da CPU na visualização de Árvore de Chamadas da ferramenta de Uso da CPU.

Alterne para a exibição do Flame Graph para conferir outra visualização da melhoria. Nesse modo de exibição, GetBlogTitleX também usa uma parte menor da CPU.

Captura de tela do uso aprimorado da CPU na visualização Gráfico de Chama da ferramenta de Uso da CPU.

Verifique os resultados no rastreamento da ferramenta Banco de Dados e apenas dois registros são lidos usando essa consulta, em vez de 100.000! Além disso, a consulta é muito simplificada e elimina o LEFT JOIN desnecessário que foi gerado anteriormente.

Captura de tela do tempo de consulta mais rápido na ferramenta Banco de Dados.

Em seguida, verificamos novamente os resultados na ferramenta de Alocação de Objetos do .NET, e vemos que GetBlogTitleX é responsável apenas por 56.000 alocações de objetos, uma redução de quase 95% de 900.000!

Captura de tela das alocações de memória reduzidas na ferramenta de Alocação de Objetos do .NET.

ITERAR

Várias otimizações podem ser necessárias e podemos continuar a iterar com alterações de código para ver quais alterações melhoram o desempenho e ajudam a reduzir o custo de computação.

Próximas etapas

Os artigos e postagens de blog a seguir fornecem mais informações para ajudá-lo a aprender a usar as ferramentas de desempenho do Visual Studio com eficiência.