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.
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 () 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.
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.
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.
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.
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.
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 loopsforeach
. - 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%.
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.
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.
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!
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.
- Isolar um problema de desempenho
- Case Study: Double performance in under 30 minutes
- Improving Visual Studio performance with the new Instrumentation Tool