C# Span<T> ve Memory<T> ile Performans Optimizasyonu
C#’ta Span<T> ve Memory<T> ile performans optimizasyonunu öğrenin. Bellek yönetimi ve yüksek performanslı veri işlemleri örneklerle.
C# 7.2 ve sonrası ile gelen Span<T> ve Memory<T> yapıları, yüksek performanslı veri işleme senaryolarında gereksiz kopyalamaları önleyerek bellek erişimini optimize eder. Bu yapılar sayesinde büyük diziler, string’ler veya byte tamponları üzerinde ekstra bellek allocation oluşturmadan çalışabilirsiniz.
Span<T> Nedir?
Span<T>, bellek üzerinde dilim (slice) mantığıyla çalışan, stack tabanlı bir yapıdır.
Dizi, string veya unmanaged belleğin bir kısmına referans tutar, kopyalama yapmaz.
Ancak stack üzerinde yaşar; bu yüzden async metotlarda veya heap’e taşınan yapılarda kullanılamaz.
using System;
class Program
{
static void Main()
{
int[] sayilar = { 10, 20, 30, 40, 50 };
Span<int> dilim = sayilar.AsSpan(1, 3); // 20,30,40
dilim[0] = 99;
Console.WriteLine(string.Join(", ", sayilar));
// Çıktı: 10, 99, 30, 40, 50 — çünkü aynı belleği gösteriyor
}
}
Slice (Dilimleme) ile Bellek Kopyalamadan Erişim
Span<T>’in en önemli avantajı, veri kopyalamadan alt bölümlere erişebilmesidir.
Örneğin, büyük bir byte dizisinin belirli bir kısmında çalışmak istediğinizde yeni bir dizi oluşturmanız gerekmez.
byte[] buffer = new byte[1000];
Span<byte> baslik = buffer.AsSpan(0, 128); // başlık kısmı
Span<byte> veri = buffer.AsSpan(128); // geri kalan veri
// Bellek kopyalanmadan aynı dizinin farklı bölümleri
baslik.Clear(); // yalnızca ilk 128 baytı temizler
Stackalloc: Heap Allocation Olmadan Geçici Bellek
stackalloc ifadesiyle heap yerine stack üzerinde geçici bellek ayırabilirsiniz.
Bu bellek otomatik temizlenir ve GC (Garbage Collector) tarafından izlenmez.
Yüksek frekanslı, küçük veri işleme senaryolarında oldukça verimlidir.
Span<int> sayilar = stackalloc int[5];
for (int i = 0; i < sayilar.Length; i++)
sayilar[i] = i * 10;
Console.WriteLine(string.Join(", ", sayilar.ToArray()));
Not: stackalloc yalnızca Span<T> ile uyumludur ve sınırlı boyutlarda kullanılmalıdır.
Büyük verilerde stack overflow riski vardır.
Memory<T> Nedir?
Memory<T>, Span<T>’ın heap tabanlı ve asenkron güvenli versiyonudur.
Span<T> yalnızca stack’te yaşarken, Memory<T> heap üzerinde tutulabilir
ve async/await gibi asenkron senaryolarda güvenle kullanılabilir.
using System;
class Program
{
static async Task Main()
{
byte[] veri = new byte[1024];
Memory<byte> memory = veri.AsMemory();
await IsleAsync(memory.Slice(100, 200));
}
static async Task IsleAsync(Memory<byte> data)
{
await Task.Delay(100); // async işlem simülasyonu
var span = data.Span;
span.Fill(255); // doğrudan belleğe erişim
Console.WriteLine("Bölüm dolduruldu.");
}
}
Memory<T>’nin Span özelliği üzerinden doğrudan Span<T> erişimi yapılabilir.
ReadOnlySpan<T> ve ReadOnlyMemory<T>
Verinin sadece okunabilir olmasını istiyorsanız ReadOnlySpan<T> veya ReadOnlyMemory<T> kullanabilirsiniz.
Böylece veri kopyalanmadan erişilir ancak değiştirilemez.
ReadOnlySpan<char> span = "Merhaba Dünya".AsSpan();
Console.WriteLine(span.Slice(8)); // Dünya
Span ile String İşleme
Span<char> kullanarak string parçalama işlemlerini Substring()’den çok daha hızlı gerçekleştirebilirsiniz.
Çünkü yeni string nesnesi oluşturulmaz, sadece orijinal string’in belirli kısmına bakılır.
ReadOnlySpan<char> metin = "1234-5678-9012-3456";
ReadOnlySpan<char> son4 = metin.Slice(metin.Length - 4);
Console.WriteLine($"Son 4 hane: {son4.ToString()}");
Span ve ArraySegment Karşılaştırması
ArraySegment<T> dizinin bir bölümünü temsil eder ancak yalnızca diziler için geçerlidir.
Span<T> ise bellek, string, pointer gibi birçok veri kaynağı üzerinde çalışabilir.
Ayrıca Span<T> ref struct olduğundan, GC baskısını azaltır.
int[] sayilar = { 1, 2, 3, 4, 5 };
var segment = new ArraySegment<int>(sayilar, 1, 3);
Span<int> dilim = sayilar.AsSpan(1, 3);
Performans Karşılaştırması
Aşağıdaki örnek, klasik string parçalama ve Span<T> kullanımı arasındaki farkı gösterir.
Substring() yeni bir string nesnesi oluştururken, AsSpan() yalnızca var olan belleğe bakar.
string metin = "CSharp Performans Testi";
var parca1 = metin.Substring(7, 10); // yeni string nesnesi
ReadOnlySpan<char> parca2 = metin.AsSpan(7, 10); // kopya yok
Console.WriteLine(parca1);
Console.WriteLine(parca2.ToString());
Özellikle yüksek hacimli string veya byte işlemlerinde Span ciddi GC baskısı azaltır.
Span ve Memory Kullanımında Dikkat Edilecekler
Span<T>stack’te yaşar; async metotlarda veya lambda dışına kaçamaz.Memory<T>heap üzerinde yaşar; async senaryolarda tercih edilmelidir.- İçerdiği verinin ömrü
Span’den uzun olmalıdır (yoksa bellek bozulur). stackallocsadece küçük verilerde kullanılmalıdır (örneğin < 1KB).- Yüksek performans gereken metin, ağ, JSON, binary veri işleme senaryolarında çok etkilidir.
Örnek: Byte Dizisi Parçalama
Örneğin bir ağ paketini çözümlerken veya dosya başlıklarını okurken, büyük byte dizilerini kopyalamadan işlemek performansı artırır. Aşağıdaki örnek, bir veri paketini başlık ve gövde olarak dilimleyerek işler.
using System;
class Program
{
static void Main()
{
byte[] paket = new byte[1024];
new Random().NextBytes(paket);
Span<byte> span = paket.AsSpan();
Span<byte> baslik = span.Slice(0, 128);
Span<byte> icerik = span.Slice(128);
Console.WriteLine($"Başlık uzunluğu: {baslik.Length}");
Console.WriteLine($"İçerik uzunluğu: {icerik.Length}");
}
}
TL;DR
- Span<T>: stack tabanlı, kopyasız, hızlı bellek dilimi. (Kısa ömürlü, async ile kullanılamaz.)
- Memory<T>: heap tabanlı, async uyumlu, güvenli bellek referansı.
- ReadOnlySpan / ReadOnlyMemory: yalnızca okuma amaçlı, veri bütünlüğünü korur.
- stackalloc: küçük geçici verilerde GC baskısını sıfırlar.
- Büyük verilerde kopyalama yapmadan işlem yaparak CPU ve GC yükünü ciddi oranda azaltır.
İlişkili Makaleler
C# Diziler (Array)
C#’ta dizileri (array) öğrenin. Eleman ekleme, erişim, döngülerle gezinme ve temel array işlemleri örneklerle anlatılıyor.
C# Generic Yapıları (List<T>, Dictionary<TKey,TValue>)
C# generic yapılarını öğrenin. List<T> ve Dictionary<TKey,TValue> ile tip güvenliği, yeniden kullanılabilirlik ve pratik örnekler.
C# Memory Management ve Garbage Collector
C#’ta memory management ve garbage collector yapısını öğrenin. Bellek yaşam döngüsü, tahsis ve temizlik süreçleri anlatılıyor.
C# Source Generators Kavramı (C# 9+)
C# Source Generators kavramını öğrenin. Derleme zamanında kod üretimi ve performans avantajları örneklerle açıklanıyor.
C# Struct (Yapılar) – Class ile Farkları
C#’ta struct ve class arasındaki farkları öğrenin. Bellek modeli, kalıtım, boxing ve performans karşılaştırmalarıyla açıklanıyor.
C# Temel Veri Tipleri
C#’ta temel veri tipleri: sayısal, metinsel, mantıksal, nesne tabanlı ve nullable tiplerin kullanımı.
C# Unsafe Kod ve Pointer Kullanımı
C#’ta unsafe kod ve pointer kullanımını öğrenin. Bellek adresleri, pointer işlemleri ve düşük seviye senaryolar örneklerle anlatılıyor.