Introdução: por que a criação de perfil é importante
Todo desenvolvedor C# já passou por uma situação em que o aplicativo funciona lentamente, mas não está claro o porquê. Palpites intuitivos geralmente são errôneos: o código que parece lento pode ser rápido, e "otimizações" óbvias podem ser inúteis. É aqui que a criação de perfil (profiling) vem ao socorro — uma análise sistemática do desempenho do código para identificar gargalos.
A criação de perfil em .NET não é apenas medir o tempo de execução. É todo um conjunto de técnicas: desde um simples Stopwatch até ferramentas avançadas como BenchmarkDotNet e PerfView. Sem a criação de perfil, você corre o risco de otimizar o que não precisa, perdendo horas em mudanças inúteis.
Neste artigo, vamos analisar métodos práticos de criação de perfil de código C#, aprender a encontrar gargalos reais e entender quais ferramentas usar em cada caso específico.
1. Criação de perfil básica com Stopwatch
A maneira mais simples de medir o tempo de execução é usar a classe System.Diagnostics.Stopwatch. Ela é adequada para verificações rápidas e cenários simples, mas tem limitações: alto overhead (nanossegundos) e falta de estatísticas.
using System.Diagnostics;
public class SimpleProfiler{ public static void Measure() { var sw = Stopwatch.StartNew(); // Código a ser testado var list = Enumerable.Range(0, 10_000_000).ToList(); sw.Stop(); Console.WriteLine($"Tempo: {sw.ElapsedMilliseconds} ms"); }}Quando usar: para uma estimativa grosseira, quando você precisa de uma resposta rápida sem instalar pacotes adicionais. Não é adequado para medições precisas (por exemplo, ao comparar algoritmos).
Desvantagens: uma medição única pode ser aleatória (GC, processos em segundo plano). É necessário executar várias vezes e calcular a média — isso é exatamente o que o BenchmarkDotNet faz.
2. BenchmarkDotNet: o padrão ouro para benchmarks
BenchmarkDotNet é uma biblioteca para criar benchmarks precisos e reproduzíveis. Ela aquece automaticamente o JIT, evita a influência do GC e fornece estatísticas detalhadas.
Instalação e exemplo básico
// Instalação: dotnet add package BenchmarkDotNetusing BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;
public class StringBenchmark{ private string _text = "Hello, World!"; [Benchmark] public bool ContainsWithOrdinal() => _text.Contains("World", StringComparison.Ordinal); [Benchmark] public bool ContainsWithInvariant() => _text.Contains("World", StringComparison.InvariantCulture);}
class Program{ static void Main() => BenchmarkRunner.Run<StringBenchmark>();}O resultado mostrará que Ordinal é 2-3 vezes mais rápido. Sem o benchmark, você não notaria isso.
Recursos avançados
- Parâmetros: teste com diferentes tamanhos de dados.
- MemoryDiagnoser: mostra alocações de memória.
- Baseline: compare o desempenho com uma referência.
[MemoryDiagnoser]public class ListBenchmark{ [Params(100, 1000, 10000)] public int N; private List<int> _list; [GlobalSetup] public void Setup() => _list = Enumerable.Range(0, N).ToList(); [Benchmark(Baseline = true)] public int ForLoop() { int sum = 0; for (int i = 0; i < _list.Count; i++) sum += _list[i]; return sum; } [Benchmark] public int ForeachLoop() { int sum = 0; foreach (var item in _list) sum += item; return sum; }}3. Criação de perfil de memória e GC
Vazamentos de memória e coletas de lixo frequentes são uma causa comum de lentidão em .NET. Para análise, use: