Partilhar via


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

Reduzir seu tempo de computação significa reduzir custos, portanto, otimizar seu código pode economizar dinheiro. Este estudo de caso usa um aplicativo de exemplo com problemas de desempenho para demonstrar como usar ferramentas de criação de perfil para melhorar a eficiência. Se você quiser comparar as ferramentas de criação de perfil, consulte Qual ferramenta devo escolher?

Este estudo de caso abrange os seguintes tópicos:

  • A importância da otimização de código e o impacto que ela tem na redução dos custos de computação.
  • Como utilizar as ferramentas de criação de perfil do Visual Studio para analisar o desempenho do aplicativo.
  • Como interpretar os dados fornecidos por essas ferramentas para identificar gargalos de desempenho.
  • Como aplicar estratégias práticas para otimizar códigos, com foco no uso da CPU, alocação de memória e interações com o banco de dados.

Acompanhe e aplique essas técnicas aos seus próprios aplicativos para torná-los mais eficientes e econômicos.

Estudo de caso de otimização

O aplicativo de exemplo examinado neste estudo de caso é um aplicativo .NET projetado que executa consultas em um banco de dados de blogs e postagens de blog. Ele utiliza o Entity Framework, um ORM (Mapeamento Relacional de Objeto) para .NET conhecido, para interagir com um banco de dados local SQLite. O aplicativo é estruturado para executar um número grande de consultas, simulando um cenário do mundo real em que um aplicativo .NET precise lidar com tarefas extensas de recuperação de dados. O aplicativo de exemplo é uma versão modificada do exemplo de introdução do Entity Framework.

O principal problema de desempenho com o aplicativo de exemplo está na forma como ele gerencia recursos de computação e interage com o banco de dados. O aplicativo tem um gargalo de desempenho que afeta significativamente sua eficiência e, consequentemente, os custos de computação associados à sua execução. O problema inclui os sintomas a seguir:

  • Alto uso da CPU: os aplicativos podem executar computações ou tarefas de processamento ineficientes de uma forma que consome uma quantidade desnecessariamente grande de recursos da CPU. Isso pode levar a tempos de resposta lentos e aumento dos custos operacionais.

  • Alocação de memória ineficiente: às vezes, os aplicativos podem ter problemas relacionados ao uso e alocação de memória. Em aplicativos .NET, o gerenciamento de memória ineficiente pode levar ao aumento da coleta de lixo, e isso pode afetar o desempenho do aplicativo.

  • Sobrecarga de interação de banco de dados: aplicativos que executam um grande número de consultas em um banco de dados podem enfrentar gargalos relacionados a interações de banco de dados. Isso inclui consultas ineficientes, chamadas excessivas de banco de dados e uso insatisfatório dos recursos do Entity Framework, e tudo isso pode prejudicar o desempenho.

O estudo de caso visa abordar esses problemas utilizando as ferramentas de criação de perfil do Visual Studio para analisar o desempenho do aplicativo. Ao entender onde e como o desempenho do aplicativo pode ser melhorado, os desenvolvedores podem implementar otimizações para reduzir o uso da CPU, melhorar a eficiência da alocação de memória, simplificar as interações com o banco de dados e otimizar a utilização de recursos. O objetivo final é melhorar o desempenho geral do aplicativo, tornando-o mais eficiente e econômico para execução.

Desafio

Há vários desafios para resolver os problemas de desempenho no aplicativo .NET de exemplo. Esses desafios decorrem da complexidade do diagnóstico de gargalos de desempenho. Os principais desafios na correção dos problemas descritos são:

  • Diagnosticar gargalos de desempenho: um dos principais desafios é identificar com precisão as causas básicas dos problemas de desempenho. O alto uso da CPU, a alocação ineficiente de memória e as despesas gerais de interação com o banco de dados podem ter vários fatores contribuintes. Os desenvolvedores devem usar ferramentas de criação de perfil de forma eficaz para diagnosticar esses problemas, o que requer alguma compreensão de como essas ferramentas funcionam e como interpretar sua saída.

  • Conhecimento e restrições de recursos: finalmente, as equipes podem enfrentar restrições relacionadas ao conhecimento, experiência e recursos. Criar o perfil e otimizar um aplicativo requer habilidades e experiência específicas, e nem todas as equipes podem ter acesso imediato a esses recursos.

Para enfrentar esses desafios, é necessária uma abordagem estratégica que combine o uso eficaz de ferramentas de criação de perfil, conhecimento técnico e planejamento e testes cuidadosos. O estudo de caso tem como objetivo orientar os desenvolvedores nesse processo, fornecendo estratégias e insights para superar esses desafios e melhorar o desempenho do aplicativo.

Estratégia

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

  • Iniciamos a investigação fazendo um rastreamento de uso da CPU. A Ferramenta de uso da CPU do Visual Studio costuma ser útil para iniciar investigações de desempenho e otimizar o código para reduzir custos.
  • Em seguida, para obter informações adicionais e ajudar a isolar problemas ou melhorar o desempenho, coletamos um rastreamento usando uma das outras ferramentas de criação de perfil. Por exemplo:
    • Vamos analisar o uso de memória. Para o .NET, experimentamos a ferramenta de alocação de objetos .NET primeiro. (Alternativamente, para .NET ou C++, você pode conferir a ferramenta Uso de memória.)
    • Para ADO.NET ou Entity Framework, podemos usar a ferramenta de Banco de Dados para examinar consultas SQL, tempo de consulta preciso e muito mais.

A coleta de dados requer as seguintes tarefas:

  • Configurando o aplicativo para uma compilação de versão.
  • Selecionando a ferramenta Uso da CPU no Criador de Perfil de Desempenho (Alt+F2). (As etapas posteriores envolvem algumas das outras ferramentas.)
  • No Criador de Perfil de Desempenho, inicie o aplicativo e colete um rastreamento.

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 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). 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.

Reunir dados adicionais

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

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

Verificar o uso de 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 será surpresa neste momento que 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 é de sequências, matrizes de objetos e Int32s. Podemos ver como esses tipos são gerados examinando o código-fonte.

Verificar a consulta na ferramenta de banco de dados

No Criador de Perfil de Desempenho, selecionamos a ferramenta Banco de Dados em vez de Uso da CPU (ou selecionar ambos). Depois de coletar um rastreamento, abra a guia Consultas na página de diagnóstico. Na guia Consultas do 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ê. Podemos usar essas informações para comparação posterior.

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

Ao examinar 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 até mais do que precisamos. Vejamos o código-fonte.

Otimizar código

É hora de dar uma olhada no código-fonte GetBlogTitleX. Na ferramenta de 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 de 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 loops foreach 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, strings associadas para cada URL e valores para propriedades contidas nas postagens, como ID do blog.

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

Se estivermos usando o Copilot, selecionamos Perguntar ao 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 /optimize, para ajudar a formular 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:

  • Adicionou a Where cláusula e eliminou um dos loops foreach.
  • Projetamos apenas a propriedade Title na instrução Select, que é o necessário neste exemplo.

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

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. 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 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 a LEFT JOIN desnecessária que foi gerada anteriormente.

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

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

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

ITERAR

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

Próximas etapas

Os artigos e as postagens no blog apresentados a seguir fornecem mais informações para ajudar você a aprender a usar as ferramentas de desempenho do Visual Studio de maneira efetiva.