Partilhar via


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

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

Este guia abrange:

  • Como usar 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, alocação de memória e interações com o banco de dados.

Aplique estas técnicas para tornar as suas próprias aplicações 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 é baseado 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 deficiente leva à coleta excessiva de lixo e ao desempenho reduzido.
  • Despesas gerais do banco de dados: consultas ineficientes e chamadas excessivas ao banco de dados degradam 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

A correção desses problemas de desempenho envolve vários desafios:

  • Diagnóstico de gargalos: Identificar as causas profundas de altas sobrecargas de CPU, memória ou base de dados requer o uso eficaz de ferramentas de análise de desempenho e interpretação correta dos resultados.
  • Restrições de conhecimento e recursos: A criação de perfis e a otimização exigem habilidades e experiência específicas, que nem sempre estão disponíveis.

Uma abordagem estratégica que combine ferramentas de definição de perfis, 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 recolha de dados requer as seguintes tarefas:

  • Defina o aplicativo como Release build.
  • Selecione a ferramenta Uso da CPU no Performance Profiler (Alt+F2).
  • No Performance Profiler, inicie a aplicação e recolha um traço.

Inspecione á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 o inicial .diagsession página de relatório que mostra dados resumidos. Utilize o link Abrir detalhes no relatório.

Captura de ecrã dos detalhes de inicialização na ferramenta Uso da CPU.

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

Na visualização da Árvore de Chamadas do, pode ver a grande utilização da CPU pelo método GetBlogTitleX no aplicativo, correspondendo a cerca de 60% do uso total da CPU do aplicativo. No entanto, o valor Self CPU para GetBlogTitleX é baixo, apenas cerca de .10%. Ao contrário, Total CPU, o valor Self CPU exclui o tempo gasto em outras funções, por isso devemos procurar mais abaixo na árvore de chamadas para encontrar o real gargalo.

Captura de ecrã da vista Árvore de Chamadas na ferramenta Utilização da CPU.

GetBlogTitleX realiza chamadas externas para duas DLLs LINQ, que estão a utilizar a maior parte do tempo da CPU, como é evidenciado pelos valores muito altos de Self CPU. Esta é a primeira pista de que uma consulta LINQ pode ser uma área a ser otimizada.

Captura de ecrã da vista Árvore de Chamadas na ferramenta Utilização da CPU com CPU Própria realçada.

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

Captura de ecrã da vista do Gráfico de Chama na ferramenta Utilização da CPU.

Reunir dados adicionais

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

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

Verificar o uso da memória

Para ver o que se passa com a aplicação em termos de utilização de memória, recolhemos um rasto usando a ferramenta .NET Object Allocation (para C++, pode usar a ferramenta Utilização de Memória). A visualização da Á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 objetos, na verdade.

Captura de tela do modo de exibição Árvore de Chamadas na ferramenta de Alocação de Objetos .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.

Verifique a consulta na ferramenta Banco de dados

No Performance Profiler, selecionamos a ferramenta Banco de dados em vez de Uso da CPU (ou selecione ambos). Quando tivermos recolhido um rasto, 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 Registos mostra quantos registos a consulta lê. Você pode usar essas informações para comparação posterior.

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

Ao examinar a instrução SELECT gerada pelo LINQ na coluna Query, 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. Vejamos 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 do GetBlogTitleX, encontramos o seguinte código que usa o 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}");
      }
  }
}

Este código usa foreach loops para pesquisar no banco de dados por quaisquer blogs 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 de blog.

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

Se estivermos usando o Copilot, selecionamos Ask Copilot no menu de contexto e digitamos a seguinte pergunta:

Can you make the LINQ query in this method faster?

Dica

Você pode usar comandos de barra como /otimize para ajudar a formar boas perguntas para o Copilot.

Neste exemplo, o 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}");
    }
}

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

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

Em seguida, testamos novamente usando as ferramentas de criação de perfil.

Resultados obtidos

Depois de atualizar o código, executamos novamente a ferramenta de Utilização da CPU para coletar um registo de execução. A visualização Call Tree mostra que GetBlogTitleX está executando apenas 1754 ms, usando 37% do total de CPU do aplicativo, uma melhoria significativa de 59%.

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

Alterne para a vista Flame Graph para ver outra visualização que mostra a melhoria. Nessa 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 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 de um tempo de consulta mais rápido na ferramenta Banco de dados.

Em seguida, verificamos novamente os resultados na ferramenta de alocação de objetos .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 de alocações de memória reduzidas na ferramenta de alocação de objetos .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óximos passos

Os seguintes artigos e postagens de blog fornecem mais informações para ajudá-lo a aprender a usar as ferramentas de desempenho do Visual Studio de forma eficaz.