.NET Performans & Bellek Yönetimi 4 dk okuma 11 okunma

.NET'te Span<T> ve Memory<T> ile Düşük Seviye Optimizasyon

Span<T> ve Memory<T>, .NET'te bellek ayırma maliyetini sıfıra indirerek (zero-allocation) yüksek performanslı kod yazmayı sağlar. Span<T> senkron işlemlerde ve stack üzerinde çalışırken, Memory<T> asenkron akışlarda ve heap'te saklanabilir. Bu yazıda, iki yapının farkları, slicing, stackalloc, ArrayPool entegrasyonu ve üretim ortamı için kullanım kuralları ele alınır.

.NET'te Span<T> ve Memory<T> ile Düşük Seviye Optimizasyon

.NET'te Span<T> ve Memory<T> ile Düşük Seviye Optimizasyon

.NET ekosisteminde performans darboğazlarının büyük çoğunluğu, gereksiz bellek tahsislerinden (allocation) kaynaklanır. Her new çağrısı, Garbage Collector (GC) üzerinde baskı oluşturur; yoğun trafikli bir API'de bu baskı, gecikme sıçramalarına ve CPU tüketimine dönüşür. Span<T> ve Memory<T>, tam bu noktada devreye girer: mevcut belleğin bir bölümüne kopyalama yapmadan erişmenizi sağlayarak, sıfır tahsisli (zero-allocation) kod yazmanıza olanak tanır .


1. Span<T> ve Memory<T> Nedir?

Her iki yapı da bitişik bir bellek bloğunun görünümünü (view) temsil eder. Bir dizi, string veya stack üzerinde ayrılmış bir alanın belirli bir kesitine, veriyi kopyalamadan erişirsiniz .

Temel Farklar:

Özellik Span<T> Memory<T>
Bellek Konumu Yalnızca stack (ref struct) Heap'te saklanabilir
Asenkron Uyumluluk ❌ await sınırını geçemez ✅ Asenkron metotlarda kullanılabilir
Kullanım Alanı Senkron işlemler, parsing, formatlama Asenkron I/O, sınıf alanı, cache
Sınırlama Sınıf alanı olamaz, boxing yapılamaz ArraySegment ile uyumludur

Neden Önemli? Substring veya Split gibi geleneksel string metotları, her çağrıda yeni bir string nesnesi oluşturur. Bir log satırından request ID çıkarmak gibi milyonlarca kez tekrarlanan bir işlemde bu, GC'yi sürekli meşgul eder. Span<T>.Slice ise aynı işlemi hiç tahsis yapmadan gerçekleştirir; benchmark sonuçlarına göre Substring'e kıyasla 7,5 kata kadar daha hızlıdır .


2. Span<T> ile Zero-Allocation Teknikleri

A. String Parsing (Slice ile)

csharp

public bool TryParseKeyValue(ReadOnlySpan<char> line, out string key, out string value)
{
    key = string.Empty;
    value = string.Empty;
    int colonIndex = line.IndexOf(':');
    if (colonIndex == -1) return false;

    // Sadece format doğrulandıktan sonra string tahsis et
    key = new string(line.Slice(0, colonIndex).Trim());
    value = new string(line.Slice(colonIndex + 1).Trim());
    return true;
}

Bu desen, özellikle HTTP header'ları, log satırları ve CSV parsing'de kritiktir .

B. stackalloc ile Yığın Üzerinde Geçici Buffer

csharp

Span<char> buffer = stackalloc char[256];
var written = FormatInto(buffer);
var message = new string(buffer.Slice(0, written));

stackalloc, heap yerine stack üzerinde bellek ayırır; GC baskısı sıfırdır. Dikkat: [SkipLocalsInit] özniteliği, .NET'in varsayılan sıfırlama işlemini atlar; ancak buffer'a yazmadan okuma yaparsanız çöp veri alırsınız. Sadece profilleme ile sıfırlamanın darboğaz olduğu kanıtlanmışsa kullanın .

C. ArrayPool ile Büyük Geçici Buffer'lar

stackalloc yalnızca küçük boyutlar için uygundur (tipik olarak <1KB). Daha büyük buffer'lar için ArrayPool<T>.Shared kullanın:

csharp

var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
    while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(), ct)) > 0)
    {
        // buffer üzerinde çalış
    }
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

Bu desen, büyük dosya işleme veya ağ akışlarında GC'yi korur .


3. Memory<T> ile Asenkron İşlemler

Span<T> bir ref struct olduğu için await sınırını geçemez. Asenkron I/O işlemlerinde Memory<T> kullanılır:

csharp

public async Task<int> ReadDataAsync(Memory<byte> buffer, CancellationToken ct)
{
    return await _stream.ReadAsync(buffer, ct);
}

Memory<T>, Span<T>'e dönüştürülebilir (memory.Span), ancak tersi mümkün değildir .


4. Kritik Kullanım Kuralları

Microsoft'un resmi yönergelerine göre :

  • Kural #1: Senkron API'lerde parametre olarak Span<T> tercih edin; Memory<T>'den daha esnek ve performanslıdır.

  • Kural #2: Buffer salt okunur olacaksa ReadOnlySpan<T> veya ReadOnlyMemory<T> kullanın. Bu, derleyiciye optimizasyon fırsatı verir.

  • Kural #3: Memory<T> parametresi alan bir metot, void döndüğünde buffer'ı artık kullanmamalıdır; aksi halde sahiplik ihlali oluşur.

  • Sahiplik (Ownership): Bir buffer'ın her zaman bir sahibi vardır. IMemoryOwner<T> ile açık sahiplik yönetimi yapılabilir; sahipsiz örneklerde GC örtük sahip sayılır .


5. Gerçek Dünya Etkisi

Brezilya'daki bir üniversite çalışmasında, finansal veri işleme (banka işlemleri ve BoletoNet kütüphanesi) senaryolarında Span<T> ve Memory<T> kullanımı test edilmiştir. Sonuçlar: birden fazla senaryoda sıfır ek kopya, belirgin hız artışı ve yanıt sürelerinde daha yüksek kararlılık . Bir log parsing benchmark'ında ise Span tabanlı Slice, Substring'e göre 7,5 kat daha hızlı ve sıfır allocation üretmiştir .


6. En İyi Pratikler

Pratik Açıklama
Sıcak yollarda Span kullanın HTTP pipeline, JSON parser, log işleme gibi yüksek frekanslı alanlarda .
Asenkron için Memory kullanın await geçen metotlarda Span yerine Memory tercih edin .
ReadOnly tercih edin Değiştirilmeyecek buffer'larda ReadOnlySpan/ReadOnlyMemory kullanın .
ArrayPool ile büyük buffer yönetin 1KB üzeri geçici buffer'larda ArrayPool kullanın .
stackalloc'u sınırlı tutun Yalnızca küçük, kısa ömürlü buffer'larda ve SkipLocalsInit riskini bilerek kullanın .

Sonuç

Span<T> ve Memory<T>, .NET'te düşük seviye optimizasyonun temel araçlarıdır. Span<T>, senkron ve stack tabanlı işlemlerde sıfır tahsisli erişim sağlarken; Memory<T>, asenkron akışlarda ve heap'te saklanması gereken buffer'larda aynı avantajı sunar. Doğru kullanıldığında, GC baskısını dramatik biçimde azaltır ve yüksek trafikli sistemlerde ölçülebilir gecikme iyileşmeleri sağlar. Ancak bu araçlar, "her yerde kullanılacak" gümüş kurşunlar değildir; sahiplik kurallarına ve senkron/asenkron ayrımına dikkat edilmezse, çalışma zamanı hatalarına yol açabilir. Unutmayın: Optimizasyon, ölçümle başlar; Span<T> ve Memory<T>, ölçüm sonrası en güçlü silahınızdır.

Tüm yazılar Soru ya da yorumun mu var? Yaz