Skip to content

Performance e Otimização para .NET Senior

📋 Conceitos Fundamentais

Por que Performance é Crítica?

  • User Experience: Tempo de resposta afeta diretamente a satisfação do usuário
  • Scalability: Sistemas otimizados suportam mais carga
  • Cost Efficiency: Menor uso de recursos = menor custo
  • Competitive Advantage: Performance pode ser um diferencial

Métricas de Performance

  • Response Time: Tempo de resposta das requisições
  • Throughput: Número de requisições por segundo
  • Memory Usage: Uso de memória e garbage collection
  • CPU Usage: Utilização do processador
  • I/O Operations: Operações de entrada/saída

🚀 Otimizações de Código

1. Memory Management

Boxing e Unboxing

csharp
// ❌ Evitar - Boxing desnecessário
public void ProcessData(object data)
{
    if (data is int value)
    {
        ProcessInt(value); // Unboxing
    }
}

// ✅ Melhor - Usar generics
public void ProcessData<T>(T data)
{
    if (data is int value)
    {
        ProcessInt(value); // Sem boxing
    }
}

// ✅ Melhor ainda - Overload específico
public void ProcessData(int data)
{
    ProcessInt(data); // Sem boxing
}

String Operations

csharp
// ❌ Evitar - Concatenação ineficiente
public string BuildMessage(string[] parts)
{
    string result = "";
    foreach (var part in parts)
    {
        result += part + " "; // Cria nova string a cada iteração
    }
    return result;
}

// ✅ Melhor - StringBuilder
public string BuildMessage(string[] parts)
{
    var sb = new StringBuilder();
    foreach (var part in parts)
    {
        sb.Append(part).Append(" ");
    }
    return sb.ToString();
}

// ✅ Melhor ainda - String.Join
public string BuildMessage(string[] parts)
{
    return string.Join(" ", parts);
}

Collections Performance

csharp
// ❌ Evitar - Lista sem tamanho inicial
public List<int> CreateList()
{
    var list = new List<int>(); // Capacidade inicial = 0
    for (int i = 0; i < 1000; i++)
    {
        list.Add(i); // Redimensiona várias vezes
    }
    return list;
}

// ✅ Melhor - Especificar capacidade inicial
public List<int> CreateList()
{
    var list = new List<int>(1000); // Capacidade inicial = 1000
    for (int i = 0; i < 1000; i++)
    {
        list.Add(i); // Sem redimensionamento
    }
    return list;
}

// ✅ Melhor ainda - Array se tamanho fixo
public int[] CreateArray()
{
    var array = new int[1000];
    for (int i = 0; i < 1000; i++)
    {
        array[i] = i; // Mais rápido que List
    }
    return array;
}

2. LINQ Otimizações

Eager vs Lazy Evaluation

csharp
// ❌ Evitar - Múltiplas iterações
public List<string> GetFilteredNames(List<Person> people)
{
    var filtered = people.Where(p => p.Age > 18); // Lazy
    var names = filtered.Select(p => p.Name); // Lazy
    return names.ToList(); // Executa ambas as queries
}

// ✅ Melhor - Single iteration
public List<string> GetFilteredNames(List<Person> people)
{
    var result = new List<string>();
    foreach (var person in people)
    {
        if (person.Age > 18)
        {
            result.Add(person.Name);
        }
    }
    return result;
}

// ✅ Melhor ainda - LINQ otimizado
public List<string> GetFilteredNames(List<Person> people)
{
    return people.Where(p => p.Age > 18)
                 .Select(p => p.Name)
                 .ToList(); // Single iteration
}

Compiled Queries

csharp
// ❌ Evitar - Query recompilada a cada execução
public async Task<List<Order>> GetOrdersByCustomer(int customerId)
{
    return await _context.Orders
        .Where(o => o.CustomerId == customerId)
        .ToListAsync();
}

// ✅ Melhor - Compiled Query
private static readonly Func<ApplicationDbContext, int, Task<List<Order>>> 
    GetOrdersByCustomerCompiled = 
        EF.CompileAsyncQuery((ApplicationDbContext context, int customerId) =>
            context.Orders.Where(o => o.CustomerId == customerId));

public async Task<List<Order>> GetOrdersByCustomer(int customerId)
{
    return await GetOrdersByCustomerCompiled(_context, customerId);
}

3. Async/Await Patterns

ConfigureAwait

csharp
// ❌ Evitar - Pode causar deadlock em alguns cenários
public async Task<string> GetDataAsync()
{
    var result = await _httpClient.GetStringAsync("http://api.example.com/data");
    return result;
}

// ✅ Melhor - Usar ConfigureAwait(false)
public async Task<string> GetDataAsync()
{
    var result = await _httpClient.GetStringAsync("http://api.example.com/data")
                                 .ConfigureAwait(false);
    return result;
}

Cancellation Tokens

csharp
// ✅ Boa prática - Suportar cancellation
public async Task<List<Order>> GetOrdersAsync(CancellationToken cancellationToken = default)
{
    return await _context.Orders
        .Where(o => o.Status == OrderStatus.Pending)
        .ToListAsync(cancellationToken);
}

// ✅ Uso com timeout
public async Task<List<Order>> GetOrdersWithTimeoutAsync()
{
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
    return await GetOrdersAsync(cts.Token);
}

🗄️ Database Performance

1. Query Optimization

Índices

sql
-- ✅ Índice composto para queries complexas
CREATE INDEX IX_Orders_CustomerId_Status_CreatedDate 
ON Orders (CustomerId, Status, CreatedDate);

-- ✅ Índice incluindo colunas frequentemente selecionadas
CREATE INDEX IX_Orders_CustomerId_Include_TotalAmount 
ON Orders (CustomerId) 
INCLUDE (TotalAmount, Status);

Query Patterns

csharp
// ❌ Evitar - N+1 Query Problem
public async Task<List<OrderDto>> GetOrdersWithCustomerInfo()
{
    var orders = await _context.Orders.ToListAsync();
    var result = new List<OrderDto>();
    
    foreach (var order in orders)
    {
        var customer = await _context.Customers.FindAsync(order.CustomerId); // N+1
        result.Add(new OrderDto { Order = order, Customer = customer });
    }
    
    return result;
}

// ✅ Melhor - Include para eager loading
public async Task<List<OrderDto>> GetOrdersWithCustomerInfo()
{
    var orders = await _context.Orders
        .Include(o => o.Customer)
        .Select(o => new OrderDto 
        { 
            Order = o, 
            Customer = o.Customer 
        })
        .ToListAsync();
    
    return orders;
}

2. Connection Pooling

csharp
// ✅ Configuração de connection pooling
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
            sqlOptions =>
            {
                sqlOptions.MaxBatchSize(100);
                sqlOptions.EnableRetryOnFailure(
                    maxRetryCount: 3,
                    maxRetryDelay: TimeSpan.FromSeconds(30),
                    errorNumbersToAdd: null);
            }));
}

3. Caching Strategies

Memory Cache

csharp
public class CachedOrderService : IOrderService
{
    private readonly IOrderService _orderService;
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(10);

    public async Task<Order> GetOrderAsync(int orderId)
    {
        var cacheKey = $"order:{orderId}";
        
        if (_cache.TryGetValue(cacheKey, out Order cachedOrder))
        {
            return cachedOrder;
        }

        var order = await _orderService.GetOrderAsync(orderId);
        
        var cacheOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(_cacheExpiration)
            .SetSlidingExpiration(TimeSpan.FromMinutes(5));

        _cache.Set(cacheKey, order, cacheOptions);
        
        return order;
    }
}

Distributed Cache (Redis)

csharp
public class RedisOrderService : IOrderService
{
    private readonly IOrderService _orderService;
    private readonly IDistributedCache _cache;
    private readonly ILogger<RedisOrderService> _logger;

    public async Task<Order> GetOrderAsync(int orderId)
    {
        var cacheKey = $"order:{orderId}";
        var cachedData = await _cache.GetStringAsync(cacheKey);
        
        if (!string.IsNullOrEmpty(cachedData))
        {
            return JsonSerializer.Deserialize<Order>(cachedData);
        }

        var order = await _orderService.GetOrderAsync(orderId);
        
        var options = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
            SlidingExpiration = TimeSpan.FromMinutes(10)
        };

        await _cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(order), options);
        
        return order;
    }
}

🔧 Profiling e Benchmarking

1. BenchmarkDotNet

csharp
[SimpleJob]
[MemoryDiagnoser]
public class StringConcatenationBenchmark
{
    private string[] _data;

    [GlobalSetup]
    public void Setup()
    {
        _data = Enumerable.Range(1, 1000)
                         .Select(i => $"Item{i}")
                         .ToArray();
    }

    [Benchmark]
    public string StringConcatenation()
    {
        string result = "";
        foreach (var item in _data)
        {
            result += item + " ";
        }
        return result;
    }

    [Benchmark]
    public string StringBuilder()
    {
        var sb = new StringBuilder();
        foreach (var item in _data)
        {
            sb.Append(item).Append(" ");
        }
        return sb.ToString();
    }

    [Benchmark]
    public string StringJoin()
    {
        return string.Join(" ", _data);
    }
}

2. Performance Counters

csharp
public class PerformanceMonitor
{
    private readonly PerformanceCounter _cpuCounter;
    private readonly PerformanceCounter _memoryCounter;

    public PerformanceMonitor()
    {
        _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
        _memoryCounter = new PerformanceCounter("Memory", "Available MBytes");
    }

    public float GetCpuUsage()
    {
        return _cpuCounter.NextValue();
    }

    public float GetAvailableMemory()
    {
        return _memoryCounter.NextValue();
    }
}

3. Application Insights Profiling

csharp
public class ProfiledService
{
    private readonly TelemetryClient _telemetryClient;

    public async Task<Order> ProcessOrderAsync(Order order)
    {
        using var operation = _telemetryClient.StartOperation<RequestTelemetry>("ProcessOrder");
        
        try
        {
            // Simular processamento
            await Task.Delay(100);
            
            operation.Telemetry.Success = true;
            return order;
        }
        catch (Exception ex)
        {
            operation.Telemetry.Success = false;
            _telemetryClient.TrackException(ex);
            throw;
        }
    }
}

🚀 Otimizações de ASP.NET Core

1. Response Caching

csharp
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    [ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productService.GetProductAsync(id);
        return Ok(product);
    }

    [HttpGet("list")]
    [ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "category", "page" })]
    public async Task<ActionResult<List<Product>>> GetProducts(string category, int page = 1)
    {
        var products = await _productService.GetProductsAsync(category, page);
        return Ok(products);
    }
}

2. Output Caching

csharp
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOutputCache(options =>
        {
            options.AddBasePolicy(builder =>
                builder.Expire(TimeSpan.FromMinutes(10)));
            
            options.AddPolicy("ShortCache", builder =>
                builder.Expire(TimeSpan.FromMinutes(1)));
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseOutputCache();
    }
}

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    [OutputCache(PolicyName = "ShortCache")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _productService.GetProductAsync(id);
        return Ok(product);
    }
}

3. Compression

csharp
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddResponseCompression(options =>
        {
            options.EnableForHttps = true;
            options.Providers.Add<BrotliCompressionProvider>();
            options.Providers.Add<GzipCompressionProvider>();
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseResponseCompression();
    }
}

📊 Monitoring Performance

1. Custom Metrics

csharp
public class PerformanceMetrics
{
    private readonly Counter _requestCounter;
    private readonly Histogram _responseTimeHistogram;
    private readonly Gauge _activeConnectionsGauge;

    public PerformanceMetrics()
    {
        _requestCounter = Metrics.CreateCounter("http_requests_total", "Total HTTP requests");
        _responseTimeHistogram = Metrics.CreateHistogram("http_request_duration_seconds", 
            "HTTP request duration");
        _activeConnectionsGauge = Metrics.CreateGauge("active_connections", 
            "Number of active connections");
    }

    public void IncrementRequestCount(string method, string path, int statusCode)
    {
        _requestCounter.WithLabels(method, path, statusCode.ToString()).Inc();
    }

    public IDisposable MeasureResponseTime()
    {
        return _responseTimeHistogram.NewTimer();
    }

    public void SetActiveConnections(int count)
    {
        _activeConnectionsGauge.Set(count);
    }
}

2. Health Checks

csharp
public class PerformanceHealthCheck : IHealthCheck
{
    private readonly PerformanceMetrics _metrics;

    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        var avgResponseTime = _metrics.GetAverageResponseTime();
        
        if (avgResponseTime > TimeSpan.FromSeconds(2))
        {
            return HealthCheckResult.Degraded($"Average response time is {avgResponseTime.TotalMilliseconds}ms");
        }

        return HealthCheckResult.Healthy();
    }
}

🎯 Melhores Práticas

1. Código

  • Use StringBuilder para concatenações múltiplas
  • Prefira string.Join para arrays
  • Use ConfigureAwait(false) em libraries
  • Implemente IDisposable corretamente
  • Use Span<T> para operações de baixo nível

2. Database

  • Crie índices apropriados
  • Use Include para evitar N+1 queries
  • Implemente connection pooling
  • Use compiled queries para queries frequentes
  • Monitore query performance

3. Caching

  • Cache dados frequentemente acessados
  • Use cache distribuído para escalabilidade
  • Implemente cache invalidation strategies
  • Monitore cache hit rates

4. Monitoring

  • Configure alertas para métricas críticas
  • Use profiling para identificar bottlenecks
  • Monitore garbage collection
  • Track business metrics

5. Infrastructure

  • Use load balancing
  • Implemente auto-scaling
  • Configure CDN para conteúdo estático
  • Use compression para reduzir payload

📋 Checklist de Performance

✅ Código

  • [ ] Otimizar loops e iterações
  • [ ] Usar estruturas de dados apropriadas
  • [ ] Implementar async/await corretamente
  • [ ] Evitar boxing/unboxing desnecessário
  • [ ] Otimizar string operations

✅ Database

  • [ ] Criar índices apropriados
  • [ ] Otimizar queries
  • [ ] Implementar connection pooling
  • [ ] Usar compiled queries
  • [ ] Monitorar query performance

✅ Caching

  • [ ] Implementar memory cache
  • [ ] Configurar distributed cache
  • [ ] Definir cache invalidation
  • [ ] Monitorar cache hit rates
  • [ ] Otimizar cache keys

✅ Infrastructure

  • [ ] Configurar load balancing
  • [ ] Implementar auto-scaling
  • [ ] Configurar CDN
  • [ ] Otimizar network latency
  • [ ] Monitorar resource usage

✅ Monitoring

  • [ ] Configurar performance counters
  • [ ] Implementar custom metrics
  • [ ] Configurar alertas
  • [ ] Monitorar business metrics
  • [ ] Implementar profiling