Comece pelo sintoma e pelo orçamento
“A aplicação está lenta” ainda não é um diagnóstico. Delimite qual operação, para quais usuários, em que horário e desde quando. Defina também o que significa rápido para aquela jornada: p95 abaixo de um orçamento, taxa de erro dentro do SLO, ou volume processado por segundo.
Uma requisição pode gastar tempo no código, em consultas, chamadas HTTP, filas, serialização ou espera por recursos. Compare duração e taxa de erro com CPU, memória, conexões e traces. Uma média isolada costuma esconder tanto picos quanto diferenças entre endpoints.
Um ciclo simples de otimização
- Reproduza o cenário com entrada e volume representativos.
- Registre uma linha de base: latência, throughput, erros e, quando relevante, alocações e CPU.
- Use logs, traces, contadores ou um profiler para localizar onde o tempo e os recursos são gastos.
- Escolha uma hipótese específica e faça uma alteração por vez.
- Repita o mesmo cenário, compare os resultados e mantenha a mudança apenas se houver benefício sem regressão importante.
Esse ciclo evita otimizar uma linha que não participa do gargalo. Para comparar operações pequenas, use BenchmarkDotNet; para investigar um processo em execução, veja diagnóstico com ferramentas .NET.
Melhorias comuns no trabalho cotidiano
Busque e transporte somente o necessário
Em endpoints de leitura, limite colunas, resultados e tamanho de página. Evite carregar entidades inteiras quando a resposta só precisa de um resumo. Procure consultas repetidas, N+1 e índices ausentes com telemetria e plano de execução, não por tentativa. O artigo de performance no EF Core e SQL aprofunda esse caminho.
Não bloqueie operações de I/O
Prefira APIs assíncronas de banco, rede e arquivos até o limite da chamada. Evite .Result, .Wait() e envolver I/O síncrono em Task.Run: isso não torna o I/O assíncrono e pode ocupar threads do pool sem necessidade. Veja async, concorrência e ThreadPool.
Reduza trabalho repetido com intenção
Evite recalcular o mesmo valor dentro de um loop quando pode calculá-lo uma vez fora. Reutilize recursos caros por meio dos mecanismos apropriados, como conexões gerenciadas por factories e clientes HTTP configurados. Cache pode reduzir trabalho, mas só é uma otimização quando expiração, invalidação, consistência e falhas estão resolvidas.
Observe alocações em caminhos quentes
Conversões, cópias de coleções, materializações e grandes strings podem criar pressão de GC em operações muito frequentes. Primeiro confirme que alocação é relevante; depois avalie uma representação mais enxuta. Memória e Garbage Collector em .NET discute quando essas mudanças ajudam e quando apenas complicam.
Faça respostas e cargas proporcionais
Paginação, compressão apropriada e contratos de resposta menores reduzem tráfego e trabalho de serialização. Verifique o custo para CPU antes de habilitar compressão em toda resposta. Para entender comportamento sob tráfego, combine métricas de serviço com testes de desempenho com k6.
Benchmark não é medição de produção
Benchmark ajuda a comparar alternativas isoladas sob condições controladas; teste de carga avalia comportamento do sistema com tráfego; telemetria mostra o que acontece com usuários reais. São ferramentas complementares, não substitutas. Veja também observabilidade de aplicações .NET.
Execute comparações em Release, sem depurador, aqueça o cenário e registre runtime, hardware, versão e dados. Use repetições e compare distribuições, não apenas uma execução. Evite publicar percentuais sem contexto: diferenças pequenas podem ser ruído de máquina, JIT, frequência de CPU ou concorrência.
Atalhos que frequentemente pioram o sistema
- Trocar código legível por uma otimização sem medição reproduzível.
- Adicionar cache global sem estratégia de invalidação, limite e comportamento em falhas.
- Paralelizar chamadas sem limite e sobrecarregar dependências ou pools.
- Aplicar pooling,
Span<T>ou estruturas complexas antes de confirmar pressão de memória. - Comparar medições feitas com configurações, dados ou ambientes diferentes.
O objetivo não é minimizar cada microssegundo em qualquer trecho. É cumprir o orçamento de desempenho com código que a equipe consegue manter.
Checklist para a próxima lentidão
- Qual operação e qual percentil estão degradados?
- O problema é CPU, espera de I/O, banco, contenção, memória ou volume excessivo?
- Consigo reproduzir com a mesma versão e uma carga representativa?
- Tenho uma hipótese e uma linha de base antes de alterar?
- O resultado melhorou sem aumentar erros, custo, complexidade ou latência de outras jornadas?