NET Core’da Span

Selamlar,

A (brief) overview of Span≤T≥ – David Wengier

Bugün aslında .Net Core 2.1’den itibaren hayatımıza giren, “Span<T>” tipinden konuşacağız. Peki ne işimize yarıyor ? Gündelik kodlarımızda, string operasyonlar ile bolca uğraşırız. String değerler immutable değerlerdir. Yani yapılan her işlemde değişmeyip, kopyalama yapılır. Bu da yüksek trafik ve büyük datalarda, memoryde inanılmaz bir allocation’a ve performans kaybına neden olur. İşte Span<T> ve Memory<T>, Heap veya Stack bellekte sıralı duran bir alana, type ve memory safe olarak erişebilmemizi ve ekstra kopyalama yapmadan üzerinde işlemler yapabilmemizi sağlamaktadır. Amaç, memory’de daha az allocation’a neden olmak ve tabi ki performans. Spanlar Value Type struct yapılardır. Özellikle, string gibi maliyetli veriler ile çalışılacak ise, ayrıca değer kopyalamaya gerek kalmadan farklı string operasyonları daha performanslı ve minimum maliyetler ile yapmamızı sağlarlar. Tabii ki sadece string değil int, diziler int[ ], ve birçok tip için kullanılabilirler.

Aslında Span<T> ve Memory<T>’yi kullanmayı, Heap ve Stack bellek yönetimini arada başka bir araç kullanarak, daha performanslı ve memory’de daha az maliyetli bir şekilde yönetmek gibi düşüne bilirsiniz. Mesela yukarıdaki resimde, özel ve zahmetli string veri tipi üzerinden, bir örnek verilmiştir. “stringler” hatırlarsanız, Heap bellekte saklanıp referans adresleri, stack’de bulunur ve başka bir string değişkene atandıklarında tamamen kopyalanırlardı( Yeni bir referans adresi alırlardır). Bu da büyük datalarda, hem memory allocation’ı hem de performansı negatif yönde etkilerdi. Yukarıda “Slice()” ile değişen tek şey, Span’daki ofset bilgisidir. 0’dan => 3’e güncellenmiştir. Yukarıda görmüş olduğunuz kare, David Wengier’ın 2018’deki kaynaklarda da belirtiğim sunumuna ait olan bir sahnedir. Span<T> için, bence gerçekten tek karelik çok güzel bir özettir.

ReadOnlySpan<T>

Aşağıda “ReadOnlySpan<char>” ile plate string pointer: referans adresi, uzunluğu length ve offset değeri Span ile tutulmuştur. s değeri ReadOnly olduğu için, immutable yani sonradan değiştirilemezdir. Slice(10) ile, ilk 10 karakter kesilmiş ve geriye 1978 string değeri kalmıştır. ReadOnlySpan<char> s’de değişen tek şey, offset değerinin 0’dan 10’a güncellenmesidir. Slice işlemi sırasında, sadece Stack bellek üzerindeki ReadOnlySpan<char> s ile çalışılmıştır. Bu da performansı arttırmış ve bellek yönetimini azaltmıştır.

Span<T> ve AsSpan(start,length)

Kullanım şekline bir başka örnek olarak, aşağıdaki ilk span’a, LostNumber int[] dizisi doğrudan atanmıştır. span2’ye ise, uygun olan tipler için geçerli olan “AsSpan()” extension’ı çağrılmış ve AsSpan(start: 2, length:3) şeklinde sıralı memory adresinin bir kısmı referans gösterilmiştir. (Baştan 3. karakter’den sonra 3 karakter ilerideki 6. karaktere kadar olan kısım) span2’ye atanmıştır. Daha sonra ilgili span ve span2, konsola yazdırılmıştır.

Bir Span<char>’ı Başka Bir Span<char>’a Kopyalama:

  • Aşağıda görüldüğü gibi chrList => Span<char> spanChr, var olan chrList’in referans adresini refere etmektedir.
  • cloneChrList, spanChar.CopyTo() methodu ile kopyalanarak yeni bir referans adresi ile ilgili dizi elemanlarını kendine kopyalamaktadır.
  • spanChar.Clear() methodu ile, tüm elemanları silinmektedir.
  • “spanChar” ile aynı referans adresini paylaşan “chrList” de, doğal olarak elemanlarını kaybetmiştir.
  • Ama kopylanma sonucu farklı referans adresine sahip “cloneChrList”, bu silinme işleminden etkilenmemiştir.

Stackalloc

Stack bölgesinde tanımlı bir array’i, Span ile temsil edebilmek için “stackalloc” expression kullanılır. Aşağıdaki örnekde, numbers e numbers2 Span<int> dizileri birbirleri ile karşılaştırılmıştır. Bu iş için, Span’a özel “SequenceCompareTo()” extension methodu kullanılmıştır.

Sonuç Ekranı: İki dizinin karşılaştırılmasının sonucu olarak numbers2’nin, numbers’ın alt kümesi olduğu anlaşılmıştır.

Substring()” ve “Span<T>.Slice() arasındaki yapılan performans ve memory’de kapladığı allocation karşılaştırması sonucu, aşağıda görüldüğü gibidir. Bunun sırrı da, Span<>’da yapılan işi için ayrıca bir memory kopyalamasının yapılmaması ve var olan string değişkenin Heap’deki memory adresinin refere edilmesidir. Ayrıca GC üzerindeki yükün bu şekilde azaltılması, performans üzerinde bir başka pozitif etkidir.

Memory<T>

Span<T>’nin bir takım kısıtlamaları vardır. Nedeni ref ve struct olarak Heap bellekde allocate edememesidir. Ancak tanımlı, boyutlu belli nesneleri refere edebilmektedir.

Span’a ait Kısıtlamalar:

  • Referans edilemez. Örn: Object, dynamic , interface.
  • Asenkron işlemlerde, await ile kullanılamaz.
  • Bir sınıf içinde property olarak tanımlanamaz.

Span<T> ile yapılamayan bu işlemler için, Heap’de saklana bilen, ref struct olmayan "Memory<T>” kullanılabilir. Ayrıca aynı Span’da olduğu gibi, immutable yani değiştirilemeyen kullanım şekli için de"ReadOnlyMemory<T>"kullanılabilir.

Aşağıda görüldüğü gibi “guid.txt” stream reader ile asenkron olarak okunmaktadır. Amaç, okunan Guid’in yarısı (1/2)’si içinde geçen sayı, karakter ve “-“‘in toplam sayısının bulunmasıdır. “Memory<char>” => yerine “Span<char>” kullanılır ise, aşağıdaki “Span<T>” ile asenkron methodlar ile çalışamazsın hatasını alırsınız. Bu gibi durumlarda Memory<T> asenkron methodlar ile çalışmamıza imkan sağlar.

guid.txt : “8679e9d5-daf6-4447-b95c-715d615af041”

Spanlar kendi Enumerator structları sayesinde, elemanları arasında ileri-geri hareket edilebilseler de, IEnumerable’‘dan türemedikleri için Linq’i desteklememektedirler. Ayrıca, data manipülasyonunda refere oldukları objeyi değiştirmeleri yüzünden dikkat edilmeleri gerekmektedir. Ama memory yönetimi ve string işlemlerde ayrıca bir kopyalamaya ihtiyaç duymamaları performans anlamında ayrı bir alkışı hak etmektedir.

Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Kaynaklar:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir