İlkay İlknur

.NET Core'da Span<T> ve Memory<T> Tipleri

Mayıs 27, 2020

Bu yazıda konumuz .NET Core 2.1 ile beraber gelen ve özellikle .NET Core 3.0 ve sonrasında framework tarafından da oldukça fazla kullanılan Span<T> tipi. Span tipi en basit tanımla bellekte ardışık olarak bulunan bir bölgeye type ve memory safe olarak erişmemizi sağlayan bir value type(struct). Span ile array, string gibi tiplerin bulunduğu managed heapteki bir bölgeye erişebilirken aynı zamanda stackalloc ile yaratılan stackteki bir bölgeye de veya native memoryde bir bölgeye de erişebiliyoruz. Üstelik bu işlemlerin hepsini Span sayesine ortak API üzerinden ve yüksek performanslı bir şekilde gerçekleştirebiliyoruz.

Span'in getirdiği en büyük faydalardan biri yukarıda bahsettiğim gibi memory bölgesinin nasıl yaratıldığından bağımsız olarak bize ortak bir API sunması. Diyelim ki bir array üzerinden işlem yapan metodunuz var ama bu array ister managed heapte saklanıyor olsun ister stackalloc ile stack üzerinde yaratılmış olsun. Biz bunun için 2 farklı metot yazmak yerine span kullanarak tüm implementasyonu bu ortak tip üzerinden gerçekleştirebiliyoruz. Bunu yaparken de hem yüksek performanslı bir şekilde yapıyoruz hem de memoryde yeni bir allocationa sebep olmayarak garbage collector için arkamızda herhangi bir şey bırakmıyoruz.

Yukarıdaki iki paragrafı okuduktan sonra konu size biraz karmaşık gelmiş olabilir. Aslında biraz daha basitleştirmemiz gerekirse Span memory üzerinde belirli bir alanı gören bir pencere olarak da düşünülebilir. Bu pencere tüm arrayi görebildiği gibi, arrayin sadece bir kısmını da görebilir.

Eğer fotoğrafçılıkla ilgileniyorsanız span'i zoom lenslere de benzetebilirsiniz. İsterseniz lensle daha geniş açı çalışıp daha geniş bir alanı çekebilirken dilerseniz lensi ayarlayarak daha dar açı çalışıp sahneye daha da yakınlaşabilirsiniz. Olay aslında bundan ibaret. Şimdi isterseniz kod kısmına geçelim.

Bir arrayden Span yaratmak için şunu yapmamız yeterli.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = array;

veya eğer elimizdeki tip span kullanımını destekliyorsa AsSpan() extension metodunu kullanabiliriz.

Span<int> span = array.AsSpan();

Span tipini bir kere yarattıktan sonra index üzerinden o memory alanındaki ilgili değere ulaşmamız veya oradaki değeri değiştirmemiz mümkün.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = array.AsSpan();
int sum = 0;
for (int i = 0; i < array.Length; i++)
{
    sum += span[i];
}
Console.WriteLine($"Sum:{sum}");
//Sum:55

Yukarıda spanleri bir pencere olarak düşünebileceğimizden bahsetmiştik. Şimdiye kadar yarattığımız spanler tüm arrayi kapsayan spanlerdi. Elimizdeki arrayin sadece belirli kısmını kapsayan span yaratmak için 2 farklı yol bulunmakta. Bunlardan biri eğer elinizde memoryde saklanan tipin referansı var ise aşağıdaki gibi bir kullanımda bulunmak mümkün.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var span = array.AsSpan(start: 4, length: 5);
var span2 = new Span<int>(array: array, start: 4, length: 5);

Eğer elimizde ilgili tipin referansı değil de o tip üzerinden yaratılan bir span varsa bu span üzerindeki Slice metodunu kullanarak daha ufak bir alanı gösteren yeni bir span yaratabiliriz.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = array;
var span2 = span.Slice(start: 4, length: 5);

ReadOnlySpan<T> Tipi

ReadOnlySpan<T> tipi Span<T> karakteristiklerinin hepsini sağlarken sadece altında bulunan memorydeki değerin değiştirilememsini sağlıyor. Örneğin span yarattığımızda array içerisindeki ilgili indexteki değeri değiştirebilirken ReadOnlySpan ile bunu yapmamız mümkün değil.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Span<int> span = array;
span[0] = 300; //Kod derlenir. 
 
ReadOnlySpan<int> span2 = array;
span2[0] = 300; //Derleme hatası

Diyelim ki bir metoda elinizdeki array'in sadece belirli bir indexten sonraki elemanlarını parametre olarak göndermek istiyorsunuz ve bu metodun da arrayin elemanlarını değiştirememesini sağlamak istiyorsunuz. Bunu kolay bir şekilde ReadOnlySpan kullanarak yapabilirsiniz.

String İle ReadonlySpan<T> Kullanımı

Şimdiye kadar span kullanımına basit olması açısında array üzerinden değindik. Span kullanımının en çok işimize yarayacağı kısımlardan biri de string operasyonları. Bir stringden AsSpan extension metoduyla ReadOnlySpan yaratabiliyoruz.

var str = "This is a string";
ReadOnlySpan<char> a = str.AsSpan();

Stackalloc ile Span Kullanımı

Yazının başında stackalloc ile stack üzerinde yaratılan arraylere de Span ile erişebileceğimizden bahsetmiştik. C# 7.2 ve .NET Core 2.1 öncesinde stackalloc kullandığımızda unsafe kod yazmamız gerekiyordu.

unsafe
{
    int length = 3;
    int* numbers = stackalloc int[length];
    for (var i = 0; i < length; i++)
    {
        numbers[i] = i;
    }
}

Şu an ise span ile unsafe kod yazmadan bu alana erişebilmemiz mümkün.

int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
    numbers[i] = i;
}

Span Kısıtlamaları ve Memory<T>, ReadOnlyMemory<T>

Span yapısı gereği bir ref struct olduğu için haliyle bazı kısıtlamara sahip. ref struct olmasının tabi ki en büyük nedenlerinden biri heapte allocationa neden olmaması. Span aynı zamanda ref struct olması nedeniyle boxingi önlemek amacıyla object, dynamic veya interface variablelarına atanamıyor. Await ile de kullanılamıyor. Ayrıca bir referans tipi içerisinde bir field olamıyor. Örneğin bir class içerisinde span property koyamıyorsunuz.

Eğer yukarıdaki kısıtlara takılıp span kullanamıyorsanız Span'in ref struct olmayan ve aynı zamanda managed heapte de saklanabilen versiyonu Memory<T> ve ReadOnlyMemory<T> tiplerini kullanabilirsiniz.

var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Memory<int> memory = array;
Memory<int> memory2 = array.AsMemory();
Memory<int> memory3 = memory2.Slice(start: 2, length: 2);

Bu yazıda Span ve memory tiplerinden ve kısaca kullanımlarından bahsetmeye çalıştık. Bir sonraki yazıda bu tiplerin özellikle String işlemlerinde nasıl kullanılabileceğini ve performans olarak ne gibi farklar yaratabileceğinden bahsedeceğiz.

Bir sonraki yazıda görüşmek üzere