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. Neste artigo, mostramos como é possível usar várias ferramentas de criação de perfil para ajudar na realização dessa tarefa.

Em vez de fornecer instruções passo a passo, a intenção aqui é mostrar como usar as ferramentas de criação de perfil com eficiência e como interpretar os dados. A ferramenta de Uso da CPU pode ajudar você a coletar e visualizar onde os recursos de computação são usados em seu aplicativo. As exibições do Uso da CPU, como a árvore de chamadas e o Flame Graph, fornecem uma visualização útil da representação em gráfico para identificar onde o tempo é consumido no aplicativo. Além disso, os insights automáticos podem mostrar otimizações precisas que podem ter um grande impacto. Outras ferramentas de criação de perfil também podem ajudar você a realizar o isolamento dos problemas. Para comparar ferramentas, confira Which tool should I choose?

Iniciar uma investigação

  • Comece a investigação com um rastreamento do uso da CPU. A ferramenta CPU Usage geralmente é útil para iniciar investigações de desempenho e otimizar o código para reduzir custos.
  • Se você quiser informações adicionais para ajudar a isolar problemas ou melhorar o desempenho, considere coletar um rastreamento usando uma das outras ferramentas de criação de perfil. Por exemplo:
    • Dê uma olhada no uso de memória. No caso do .NET, experimente a ferramenta alocação de objeto do .NET primeiro. No caso do .NET ou C++, você pode examinar a ferramenta Uso de Memória.
    • Se o seu aplicativo estiver usando E/S de Arquivo, use a ferramenta de E/S de Arquivo.
    • Se estiver usando ADO.NET ou Entity Framework, você poderá tentar a ferramenta de banco de dados para examinar consultas SQL, tempo de consulta preciso, et al.

Exemplo de coleção de dados

As capturas de tela de exemplo mostradas neste artigo são baseadas em um aplicativo .NET que executa consultas em um banco de dados de blogs e postagens de blog associadas. Primeiro, você examinará um rastreamento do uso da CPU para procurar oportunidades de otimizar o código e reduzir o custo de computação. Depois de obter uma ideia geral do que está acontecendo, você também examinará os rastros de outras ferramentas de criação de perfil para ajudar a isolar os problemas.

A coleção de dados requer as seguintes etapas (não mostradas aqui):

  • Definir seu aplicativo como um build de versão
  • Selecione a ferramenta de Uso de 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

Comece coletando um rastreamento com a ferramenta de Uso da CPU. Quando os dados de diagnóstico forem carregados, verifique primeiro a página inicial do relatório .diagsession que mostra os principais insights e o Hot Path. O Caminho Crítico mostra o caminho do código com o maior uso da CPU em seu aplicativo. Essas seções podem fornecer dicas para ajudar você a identificar rapidamente problemas de desempenho que podem ser melhorados.

Você também pode ver o caminho crítico no modo de exibição Árvore de Chamadas. Para abrir esse modo de exibição, use o link Abrir detalhes no relatório e selecione Árvore de Chamadas.

Nessa exibição, você vê o atalho novamente, que mostra o alto uso da CPU para o método GetBlogTitleX no aplicativo, usando cerca de 60% do 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 no modo de exibição de á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. Esta é a primeira pista de que você pode querer procurar uma consulta LINQ como uma área para otimizar.

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 chamada visualizada e uma visualização diferente dos dados, alterne para a visualização Gráfico de Chamas (selecione na mesma lista que a Árvore de Chamadas). 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. Por exemplo, para identificar as DLLs do LINQ, primeiro experimentaremos a ferramenta Banco de Dados. Você pode selecionar várias ferramentas junto com o Uso da CPU. Depois de coletar um rastreamento, selecione a guia Consultas na página 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 pela sintaxe LINQ na coluna Consulta, você identifica 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, se necessário. 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 você está recuperando muitos valores de coluna aqui, talvez mais do que precisa.

Para ver o que está acontecendo com o aplicativo em termos de uso de memória, colete um rastreamento usando a ferramenta Alocação de Objeto .NET (para C++, use a ferramenta Uso de Memória). O modo de exibição da Árvore de Chamadas no rastreamento de memória mostra o caminho crítico e 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 são cadeias de caracteres, matrizes de objetos e Int32. Você pode ver como esses tipos são gerados examinando o código-fonte.

Otimizar código

É hora de dar uma olhada no código-fonte GetBlogTitleX. Na ferramenta Alocação de Objeto do .NET, clique com o botão direito do mouse no método e escolha Ir para 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.

Você faz uma pequena pesquisa e encontra algumas recomendações comuns sobre como otimizar as consultas LINQ e criar esse código.

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

Nesse código, você fez várias alterações para ajudar a otimizar a consulta:

  • Adicione a cláusula Where e elimine um dos loops foreach.
  • Projete apenas a propriedade Título na instrução Select, que é tudo o que você precisa neste exemplo.

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

Verificar os resultados

Depois de atualizar o código, execute 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 o modo de exibição Gráfico de Chamas para ver outra visualização do aprimoramento. 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, verifique novamente os resultados na ferramenta de Alocação de Objeto do .NET e veja que GetBlogTitleX é responsável apenas por 56.000 alocações de objeto, quase uma redução de 95% de 900.000!

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

ITERAR

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

Próximas etapas

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