Giriş

Performans iyileştirme, modern yazılım geliştirmenin can alıcı noktalarından biri. Özellikle .NET ekosistemine odaklanırsak, uygulamalarımızın hızlı çalışması sadece kullanıcı deneyimini değil, aynı zamanda sunucu maliyetlerini de doğrudan etkiliyor.

performance

Bu rehberde, junior’dan senior’a kadar her seviyeden .NET geliştiricinin kullanabileceği performans iyileştirme tekniklerini ele alacağız. Memory yönetiminden database optimizasyonuna, async programming’den caching stratejilerine kadar geniş bir yelpazede pratik çözümler sunacağım.

Temeller: Performans İyileştirme Nedir?

Performans iyileştirme, uygulamanızın daha az kaynak kullanarak daha hızlı çalışmasını sağlama sanatı. Bu sadece “daha hızlı kod yazmak” değil, sistem genelinde düşünerek bottleneck’leri tespit etmek ve çözmek demek.

.NET dünyasında performans iyileştirme üç ana eksende döner:

  • CPU kullanımı: Algoritmik optimizasyonlar ve verimli kod yazımı
  • Memory yönetimi: Garbage Collection baskısını azaltma
  • I/O işlemleri: Veritabanı, dosya sistemi ve network çağrılarının optimizasyonu

Performans iyileştirme yapmadan önce mutlaka ölçüm yapın. “Premature optimization is the root of all evil” Donald Knuth’un bu sözü hâlâ geçerli.

.NET Bağlamında Performans İyileştirme

ASP.NET Core ve modern C# ile performans iyileştirme yaparken şu alanları öncelik sırasına koyabiliriz:

ASP.NET Core Pipeline’ında:

  • Middleware sıralaması
  • Response caching
  • Minimal API vs Controller tercihleri
  • JSON serialization optimizasyonu

C# Kod Seviyesinde:

  • String manipülasyonları
  • Collection seçimleri
  • Async/await kullanımı
  • LINQ optimizasyonları

performance

Örnek Senaryo: E-ticaret API’si

Diyelim ki bir e-ticaret API’si geliştiriyorsunuz ve ürün listeleme endpoint’iniz yavaş çalışıyor. Tipik bir senaryo şöyle görünür:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    // Yavaş implementasyon - Optimize edilecek
    [HttpGet]
    public async Task<ActionResult<List<ProductDto>>> GetProducts()
    {
        var products = await _context.Products
            .Include(p => p.Category)
            .Include(p => p.Reviews)
            .ToListAsync();
        
        return Ok(products.Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            CategoryName = p.Category.Name,
            AverageRating = p.Reviews.Average(r => r.Rating)
        }).ToList());
    }
}

Bu kodda birkaç performans problemi var. N+1 query problemi, gereksiz data loading ve memory kullanımı optimizasyonsuz.

Kod Örneği: Optimize Edilmiş Versiyon

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMemoryCache _cache;
    private readonly AppDbContext _context;
    
    [HttpGet]
    public async Task<ActionResult<List<ProductDto>>> GetProducts(
        [FromQuery] int page = 1, 
        [FromQuery] int pageSize = 20)
    {
        var cacheKey = $"products_page_{page}_size_{pageSize}";
        
        if (_cache.TryGetValue(cacheKey, out List<ProductDto>? cachedProducts))
        {
            return Ok(cachedProducts);
        }
        
        // Projection ile sadece ihtiyacımız olan field'ları çekiyoruz
        var products = await _context.Products
            .AsNoTracking() // Change tracking kapatıyoruz
            .Select(p => new ProductDto
            {
                Id = p.Id,
                Name = p.Name,
                CategoryName = p.Category.Name,
                AverageRating = p.Reviews.Any() ? p.Reviews.Average(r => r.Rating) : 0
            })
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
        
        // Cache'e ekliyoruz
        _cache.Set(cacheKey, products, TimeSpan.FromMinutes(15));
        
        return Ok(products);
    }
}

Bu versiyonda şu optimizasyonları yaptık:

  • AsNoTracking(): Change tracking overhead’ini kaldırdık
  • Projection: Sadece ihtiyacımız olan field’ları çektik
  • Pagination: Büyük data setleri için sayfalama
  • Caching: Sık istenen verileri cache’ledik

İleri Konular: Derin Performans Teknikleri

performance

Memory Optimization

// Yanlış - String concatenation
public string BuildSql(List<int> ids)
{
    var sql = "SELECT * FROM Products WHERE Id IN (";
    for (int i = 0; i < ids.Count; i++)
    {
        sql += ids[i].ToString();
        if (i < ids.Count - 1) sql += ",";
    }
    sql += ")";
    return sql;
}

// Doğru - StringBuilder kullanımı
public string BuildSql(List<int> ids)
{
    var sb = new StringBuilder();
    sb.Append("SELECT * FROM Products WHERE Id IN (");
    
    for (int i = 0; i < ids.Count; i++)
    {
        sb.Append(ids[i]);
        if (i < ids.Count - 1) sb.Append(",");
    }
    
    sb.Append(")");
    return sb.ToString();
}

// En iyi - Span<T> ile modern approach
public string BuildSql(ReadOnlySpan<int> ids)
{
    return $"SELECT * FROM Products WHERE Id IN ({string.Join(",", ids.ToArray())})";
}

Async Pattern Optimizasyonu

// Yanlış - Sequential çağrılar
public async Task<UserProfileDto> GetUserProfile(int userId)
{
    var user = await _userService.GetUser(userId);
    var orders = await _orderService.GetUserOrders(userId);
    var preferences = await _preferenceService.GetPreferences(userId);
    
    return new UserProfileDto { User = user, Orders = orders, Preferences = preferences };
}

// Doğru - Parallel çağrılar
public async Task<UserProfileDto> GetUserProfile(int userId)
{
    var userTask = _userService.GetUser(userId);
    var ordersTask = _orderService.GetUserOrders(userId);
    var preferencesTask = _preferenceService.GetPreferences(userId);
    
    await Task.WhenAll(userTask, ordersTask, preferencesTask);
    
    return new UserProfileDto 
    { 
        User = await userTask, 
        Orders = await ordersTask, 
        Preferences = await preferencesTask 
    };
}

Test Edilebilirlik ve Performans

Performans testleri yaparken BenchmarkDotNet kullanımı harika bir yaklaşım:

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class StringConcatenationBenchmark
{
    private readonly List<string> _items = Enumerable.Range(1, 1000)
        .Select(i => $"Item{i}")
        .ToList();
    
    [Benchmark(Baseline = true)]
    public string UsingStringConcatenation()
    {
        string result = "";
        foreach (var item in _items)
        {
            result += item + ",";
        }
        return result;
    }
    
    [Benchmark]
    public string UsingStringBuilder()
    {
        var sb = new StringBuilder();
        foreach (var item in _items)
        {
            sb.Append(item).Append(",");
        }
        return sb.ToString();
    }
    
    [Benchmark]
    public string UsingStringJoin()
    {
        return string.Join(",", _items);
    }
}

Integration testlerde response time’ları da test edebiliriz:

[Test]
public async Task GetProducts_ShouldReturnWithinAcceptableTime()
{
    // Arrange
    var stopwatch = Stopwatch.StartNew();
    
    // Act
    var response = await _httpClient.GetAsync("/api/products?page=1&pageSize=20");
    stopwatch.Stop();
    
    // Assert
    response.StatusCode.Should().Be(HttpStatusCode.OK);
    stopwatch.ElapsedMilliseconds.Should().BeLessThan(500); // 500ms altında olmalı
}

Entegrasyon: DI ve Middleware ile Performans

Response compression middleware’i eklemek:

// Program.cs
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

// Middleware pipeline'da
app.UseResponseCompression();

Custom performance middleware:

public class PerformanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<PerformanceMiddleware> _logger;
    
    public PerformanceMiddleware(RequestDelegate next, ILogger<PerformanceMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        var stopwatch = Stopwatch.StartNew();
        
        await _next(context);
        
        stopwatch.Stop();
        
        if (stopwatch.ElapsedMilliseconds > 1000)
        {
            _logger.LogWarning("Slow request: {Method} {Path} took {ElapsedMs}ms",
                context.Request.Method,
                context.Request.Path,
                stopwatch.ElapsedMilliseconds);
        }
    }
}

Sık Hatalar ve Kaçınma Yolları

1. N+1 Query Problem:

// Yanlış
foreach (var order in orders)
{
    var customer = await _context.Customers.FindAsync(order.CustomerId);
    // Her order için ayrı query
}

// Doğru
var customerIds = orders.Select(o => o.CustomerId).Distinct();
var customers = await _context.Customers
    .Where(c => customerIds.Contains(c.Id))
    .ToListAsync();

2. Async void Kullanımı:

// Yanlış - Exception handling zorlaşır
public async void ProcessData()
{
    await SomeAsyncOperation();
}

// Doğru
public async Task ProcessData()
{
    await SomeAsyncOperation();
}

3. ConfigureAwait Unutmak:

// Library code'da mutlaka ConfigureAwait(false) kullanın
public async Task<Data> GetDataAsync()
{
    var result = await _httpClient.GetAsync(url).ConfigureAwait(false);
    return await result.Content.ReadFromJsonAsync<Data>().ConfigureAwait(false);
}

4. IDisposable Kaynak Yönetimi:

// Yanlış
public async Task ProcessFile(string path)
{
    var fileStream = new FileStream(path, FileMode.Open);
    // Dispose edilmiyor
}

// Doğru
public async Task ProcessFile(string path)
{
    using var fileStream = new FileStream(path, FileMode.Open);
    // Otomatik dispose
}

Sonuç ve Uygulama Checklist’i

Performans iyileştirme sürekli bir süreç. Burada öğrendiklerimizi özetleyecek olursak, başarılı bir performans optimizasyonu stratejisi measurement, optimization ve monitoring döngüsü üzerine kurulu olmalı.

Modern .NET geliştirme süreçlerinde performans sadece bir “nice to have” özellik değil, kullanıcı deneyimi ve sistem sürdürülebilirliği için kritik bir gereklilik.

Uygulanabilir Performans İyileştirme Checklist’i:

  1. Profiling araçları kullan – dotTrace, PerfView veya Visual Studio Diagnostic Tools ile bottleneck’leri tespit et
  2. Database sorgularını optimize et – EF Core’da AsNoTracking(), projection ve eager loading stratejilerini doğru kullan
  3. Caching stratejisi uygula – Memory cache, distributed cache ve HTTP cache’i uygun yerlerde kullan
  4. Async/await pattern’lerini doğru kullan – ConfigureAwait(false) ile deadlock’ları önle, parallel execution fırsatlarını değerlendir
  5. String manipülasyonlarını optimize et – StringBuilder, Span<T> ve StringComparison.Ordinal kullanımlarına odaklan
  6. Garbage Collection baskısını azalt – Object pooling, memory allocation’ları minimize et
  7. HTTP pipeline’ını optimize et – Response compression, HTTP/2, connection pooling ayarlarını yapılandır
  8. Database connection’ları verimli yönet – Connection pooling ve async database operations’ları tercih et
  9. JSON serialization’ı optimize et – System.Text.Json kullan ve gereksiz property’leri serialize etme
  10. Performance testleri yaz – BenchmarkDotNet ile micro-benchmarks, load testing ile gerçek dünya senaryolarını test et

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir