Kısa giriş
System.Collections.Generic.List, .NET dilinde genel bir <T>koleksiyon sınıfıdır; kolaylığı ve zengin API'si sayesinde her türlü veriyi depolayabilir; bu API günlük yaşamımızda yaygın olarak kullanılır ve en çok kullanılan koleksiyon sınıfı olarak söylenebilir.
Kod yazarken, genellikle bir Liste <T>koleksiyonunu işin içinden bazı işleriyle ilgili öğeleri elde etmek için yineleme yapmamız gerekir. Normalde bir sette çok fazla unsur yoktur ve geçmek çok hızlıdır. Ama bazı büyük veri işleme, istatistik, gerçek zamanlı hesaplama vb<T>. için on binlerce ya da yüz binlerce verinin listesini hızlıca nasıl geçebilirsiniz? Bugün sizinle paylaşmam gereken şey bu.
Geçiş modu
Farklı geçiş yöntemlerinin performansına bakalım ve farklı büyüklük mertebesi toplama işlemleriyle farklı yöntemlerin performansını görmek için aşağıdaki performans kıyaslamasını oluşturalım. Kod parçası şöyle görünüyor:
Foreach ifadesini kullanın
foreach koleksiyonları en yaygın şekilde dolaştığımızdır, iterator deseninin sözdizimi şeker uygulamasıdır ve bu dönem için bir kıyaslama olarak da kullanılır.
Foreach ifadesi bir sözdizimi şekeri olduğundan, derleyici sonunda işlevselliği uygulamak için GetEnumerator() ve MoveNext() ile while döngüsü çağırır. Derlenmiş kod şöyle görünüyor:
MoveNext() yöntemi uygulaması, iterasyonda koleksiyonu değiştiren başka iş parçacığı olmamasını sağlar ve değişiklik gerçekleşirse InvalidOperationException istisnası çıkarır, mevcut indeksin meşru olup olmadığını kontrol etmek için taşma kontrolü yapılır ve ayrıca ilgili öğeyi enumeratora ataması gerekir. Güncel özellik,Yani aslında performansı en iyi değil, kod parçası şöyle görünüyor:
Farklı set boyutlarında nasıl performans gösterdiğine bakalım ve sonuçlar şöyle görünüyor:
Farklı boyutlarda, zaman alan sürecin doğrusal büyüme ilişkisinin gerektiği görülebilir; 100w veri işlem mantığı olmadan geçilse bile en az 1 saniye sürer.
ForEach List yöntemini kullanın
Bir diğer yaygın yol ise List <T>kullanmaktır. ForEach() yöntemi, bir Eylem <T>delegesini göndermenize olanak tanır; bu temsilci öğe boyunca ilerlerken Eylem delegesini <T>çağırır.
<T>List'in dahili bir uygulama yöntemidir, böylece özel dizilere doğrudan erişebilir ve taşma kontrollerinden kaçınabilir. Teoride, hızlı olmalı; Ancak bizim senaryomuzda sadece bir boş yöntem vardır ve bu, foreach yöntemine tamamen hatır içi çağrı ile iyi davranmayabilir. Aşağıda, taşma kontrolü olmadığını, ancak eşzamanlı sürüm numarası kontrolünü koruduğuna dair ForEach yönteminin kaynak kodu yer almaktadır.
Ayrıca, ForEach metoduna bir delege iletmek gerektiğinden, çağrı kodunda kapanış üretim sınıfındaki delege nesnesinin her seferinde boş olup olmadığını kontrol edecektir, yoksa yeni Action<T>(), aşağıda gösterildiği gibi kontrol edilir:
Performans açısından foreach anahtar kelime ile nasıl karşılaştırıldığında bir bakalım. Aşağıdaki görsel, kıyaslamanın sonuçlarını göstermektedir:
Test sonuçlarına bakılırsa, foreach anahtar kelimesini doğrudan kullanmaktan %40 daha yavaş, eğer gerekli değilse foreach kelimesini doğrudan kullanmak daha iyi bir tercih gibi görünüyor, yani daha hızlı bir yol var mı?
döngü geçişi için
En eski yöntemimize geri dönelim, yani for anahtar kelimesini kullanarak koleksiyonda dolaşıyoruz. Şu anda en iyi performans gösteren geçiş yöntemi olmalı, çünkü önceki kodlar gibi yedek kod gerektirmiyor (ancak indeksleyici de taşmanı önlemek için kontrol ediliyor) ve tabii ki sürüm numarasını kontrol etmiyor, bu yüzden çok iş parçacıklı ortamda koleksiyon değiştirilir ve for kullanılırken istisna olmaz. Test kodu şöyle görünüyor:
Nasıl sonuçlanacağını görelim.
Beklediğimiz gibi görünüyor.for döngüsünü doğrudan kullanmak, foreach ile karşılaştırıldığından %60 daha hızlıdır, eskiden 1 saniye süren bir set, şimdi sadece 400 milisaniye sürüyor. Peki daha hızlı bir yol var mı?
Koleksiyonları KullanmaMarshal
.NET5'ten sonra, dotnet topluluğu, koleksiyon işlemlerinin performansını artırmak için CollectionsMarshal sınıfını uyguladı. Bu sınıf, koleksiyon türlerinin yerel dizilerine nasıl erişileceğini uygular (eğer benim [. .NET Performans Optimizasyonu - Koleksiyon Türleri için Başlangıç Boyutunu Belirlemelisiniz] makalesi, birçok veri yapısının temel uygulamasının dizileri olduğunu biliyorsunuz). Böylece her türlü tespiti atlayıp doğrudan orijinal diziye erişebiliyor, ki bu en hızlı olmalı. Kod şöyle görünüyor:
Derleyici tarafından oluşturulan kodun çok verimli olduğunu görebilirsiniz.
Altta yatan diziye doğrudan erişim çok tehlikelidir, her kod satırında ne yaptığınızı bilmeli ve yeterli test yapmalısınız. Kıyaslama sonuçları aşağıdaki gibidir:
VayCollectionsMarshal'ı kullanmak, foreach kullanmaktan %79 daha hızlıdır, ama JIT optimizasyonunun sebebi olmalı, foreach ile for keyword loop Span arasında büyük bir fark yok.
özet
Bugün size Liste koleksiyonunu hızlıca nasıl geçebileceğinizi anlattım ve çoğu durumda hem taşma kontrolü hem de çok iş parçacıklı sürüm numarası kontrolü olan foreach anahtar kelimesini kullanmak öneriliyor; bu da doğru kodu yazmamızı kolaylaştırıyor.
Yüksek performans ve büyük veri hacmi istiyorsanız, koleksiyonu doğrudan CollectionsMarshal.AsSpan olarak kullanmak önerilir.
Bu makalenin kaynak kodu bağlantısı:
Bağlantı girişi görünür.
Orijinal bağlantı:Bağlantı girişi görünür.
|