İlkay İlknur

.NET Core Span<T> ve Memory<T> Performans Karşılaştırması

Haziran 10, 2020

Bir önceki yazıda .NET Core'daki Span ve Memory tiplerinden bahsettik. Bu yazıda da bu tiplerin performans ve memory kazanımlarını ufak örneklerle inceleyeceğiz. Aslında bu yazı bir anlamda bir önceki yazıyı tamamlayacak nitelikte olacak ve eski kullanımlar yerine neden yeni gelen bu tipleri kullanmamız gerektiğini bizlere gösterecek diye düşünüyorum.

İlk senaryomuz bir array'in sadece belirli elemanlarıyla çalışma senaryosu. Diyelim ki elinizde bir array var ancak bu arrayin sadece belirli indexten sonraki elemanlarıyla bir işlem yapmak istiyorsunuz. Bunun için bir önceki makalede bahsettiğimiz gibi Span ve Memory tiplerini kullanabiliriz. Bunun dışında istediğimiz elemandan sonraki tüm değerleri yeni bir array'e kopyalayıp o array üzerinden işlemlerimizi gerçekleştirebiliriz. Şimdi gelin bu opsiyonları koda dökelim.

[MemoryDiagnoser]
[HtmlExporter]
public class Runner
{
    int[] array;
 
    [GlobalSetup]
    public void Setup()
    {
        array = Enumerable.Range(1, 100).ToArray();
    }
    [Benchmark]
    public void ArrayCopy()
    {
        var copy = new int[array.Length - 10];
        Array.Copy(array, 10, copy, 0, copy.Length);
    }
 
    [Benchmark]
    public void Span()
    {
        var span = array.AsSpan(10);
    }
 
    [Benchmark]
    public void Memory()
    {
        var memory = array.AsMemory(10);
    }
}

Benchmarkın setup kısmında basit olarak 100 elemanlı bir array yaratıyoruz. Sonrasında ilk olarak Array.Copy metodunu kullanarak array içerisindeki 10. elemandan sonraki tüm elemanları yeni bir arraye kopyalıyoruz. Diğer benchmarklarda ise 10. elemandan sonraki tüm elemanları temsil eden bir Span ve Memory yaratıyoruz. Sonuçlar ise aşağıdaki gibi.

Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
ArrayCopy 46.3849 ns 0.9564 ns 2.4342 ns 45.2222 ns 0.0612 - - 384 B
Span 0.4787 ns 0.0193 ns 0.0161 ns 0.4709 ns - - - -
Memory 0.4974 ns 0.0229 ns 0.0203 ns 0.5002 ns - - - -

Array copy senaryosunda hem yeni bir array yaratıp heap allocationa sebep oluyoruz hem de orjinal arrayden yeni yaratılan arraye elemanları kopyalıyoruz. Bu nedenle bu işlem diğer opsiyonlara göre oldukça yavaş. Ancak Span veya Memory tipleriyle gördüğünüz gibi hızlı bir şekilde arrayin ilgili bölümünü temsil eden yapıları hızlıca yaratabiliyoruz.

Span ve memorynin en önemli kullanım alanlarından biri de stringler. Diyelim ki bir stringi parse ediyorsunuz. Bunun için normalde string içerisindeki herhangi bir alanı String.Substring metodunu kullanarak alabiliriz. Ancak stringler immutable tipler oldukları için bu metotların sonucunda yeni bir string yaratılır ve bize dönülür. Bu da yeni bir allocationa neden olur. Bundan kaçınmak için de Span ve Memory kullanabiliriz. Hemen kodlara bakalım.

[MemoryDiagnoser]
[HtmlExporter]
public class Runner
{
    string text;
 
    [GlobalSetup]
    public void Setup()
    {
        text = "1234:123123123:12312311";
    }
 
    [Benchmark]
    public string Substring()
    {
        var firstIndex = text.IndexOf(':');
        var lastIndex = text.LastIndexOf(':');
        return text.Substring(firstIndex + 1, lastIndex - firstIndex - 1);
    }
 
    [Benchmark]
    public ReadOnlySpan<char> Span()
    {
        var firstIndex = text.IndexOf(':');
        var lastIndex = text.LastIndexOf(':');
        return text.AsSpan(firstIndex + 1, lastIndex - firstIndex - 1);
    }
 
    [Benchmark]
    public ReadOnlyMemory<char> Memory()
    {
        var firstIndex = text.IndexOf(':');
        var lastIndex = text.LastIndexOf(':');
        return text.AsMemory(firstIndex + 1, lastIndex - firstIndex - 1);
    }
}

Benchmark sonucu da şu şekilde.

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Substring 20.92 ns 0.106 ns 0.099 ns 0.0076 - - 48 B
Span 11.07 ns 0.036 ns 0.032 ns - - - -
Memory 13.02 ns 0.058 ns 0.051 ns - - - -

Gördüğünüz gibi bu benchmark sonunda da Span ve Memory'nin performansı Substring'e göre oldukça yüksek. Diğer bir yandan da allocationa neden olmuyor.

Bu makalede bir önceki makaleyi tamamlaması açısında Span ve Memory tiplerinin performans ve memory kullanımı açısından nasıl performanslı olabileceğini gördük. Şu anda .NET Core içerisinde çoğu API'da zaten Span ve Memory desteği bulunmakta. Ayrıca internal olarak da .NET Core içerisinde çoğu API da bu tipleri kullanarak büyük bir performans ve memory kazanımları sağlamakta. .NET Core 3.0 ile beraber gelen yeni System.Text.Json API'larının da çok performanslı çalışmasının sebeplerinden biri de yine alt katmanda bu tipleri oldukça etkin kullanması. Biz bu makalede gördüğünüz gibi sadece çok basit örnekler üzerinde performans karşılaştırması yaptık. Ancak fazla yük alan uygulamalarınızda özellikle bu tipleri doğru bir şekilde kullanmanız durumunda oldukça fazla kazanım elde etmeniz mümkün.

Bir sonraki makalede görüşmek üzere