|
|
2022-5-29 13:45:22 tarihinde yayınlandı
|
|
|
|

Kısa giriş
Performans optimizasyonu, aynı sayıda isteğin daha az kaynakla (genellikle CPU veya bellek ve tabii ki işletim sistemi IO işlemleri, ağ trafiği, disk kullanımı vb.) işlenmesini sağlar. Ama çoğu zaman, CPU ve bellek kullanımını azaltıyoruz. Daha önce paylaşılan içeriğin bazı sınırlamaları var, doğrudan dönüştürmek zor, bugün size basit bir yöntem paylaşmak istiyorum, sadece birkaç koleksiyon türünü değiştirmem gerekiyor, böylece performansı artırmak ve bellek ayak izini azaltmak için yeterli. Bugün sizinle bir sınıf kütüphanesini paylaşmak istiyorum, bunuSınıf kütüphanesi Koleksiyonlar olarak adlandırılır., Adından da anlaşılacağı üzere, bu yöntem bellek ve GC'yi azaltmak amacıyla havuzlanmış bellek aracılığıyla yapılıyor ve performansının nasıl olduğunu doğrudan göreceğiz, ayrıca kaynak kodunu da göstereceğiz, neden bu performans iyileştirmelerini getirdiğini göreceğiz.
Koleksiyonlar.Birleştirilmiş
Proje bağlantısı:Bağlantı girişi görünür.
Kütüphane, System.Collections.Generic'teki sınıflara dayanmaktadır; bu sınıflar, bellek <T><T>tahsisini azaltmak, performansı artırmak ve modern API'lerle daha fazla birlikte çalışabilirlik sağlamak amacıyla yeni System.Span ve System.Buffers.ArrayPool sınıf kütüphanelerinden faydalanmak üzere değiştirilmiştir. Collections.Pooled, .NET Standnd 2.0 (.NET Framework 4.6.1+) ile birlikte . NET Core 2.1+. CoreFX'ten kapsamlı bir birim test ve benchmark seti taşınmıştır.
Toplam test sayısı: 27501. Arka: 27501. Başarısızlık: 0. Atla: 0. Test çalışması başarılı oldu. Test uygulama süresi: 9.9019 saniye
Nasıl kullanılır
Bu kütüphaneyi Nuget üzerinden kolayca kurabilirsiniz, NuGet Versiyonu.
Collections.Pooled kütüphanesinde, .NET yerel tipleriyle karşılaştırmada gösterildiği gibi, yaygın kullandığımız koleksiyon türleri için havuzlu sürümleri uygular.
| . .NET native | Koleksiyonlar.Birleştirilmiş | Açıklama | | Liste<T> | PooledList<T> | Genel koleksiyon sınıfları | | Sözlük<TKey, TValue> | PooledDictionary<TKey, TValue> | Genel sözlük sınıfı | | HashSet<T> | PooledSet<T> | Genel hash koleksiyon sınıfları | | Yığın<T> | Yığın<T> | Genel yığınlar | | Sıra<T> | PooledQueue<T> | Genel grup |
Kullanırken sadece karşılık gelen 'yi eklememiz yeterlidir. .NET yerel sürümü ve Collections.Pooled sürümü, aşağıdaki kodda gösterildiği gibi:
Ancak, Havuzlu tipin IDispose arayüzünü uyguladığını ve kullanılan belleği Dispose() yöntemiyle havuza geri döndürdüğünü belirtmeliyiz; bu nedenle Havuzlu koleksiyon nesnesini kullandıktan sonra Dispose() yöntemini çağırmamız gerekiyor. Ya da doğrudan var anahtar kelimesini kullanabilirsin.
Not: Collections.Pooled içindeki koleksiyon nesnesini kullanınEn iyisi manuel olarak serbest bırakmaktır, ama serbest bırakmasanız da fark etmez, GC sonunda geri dönüştürür, ancak havuza geri döndürülemez ve bellek kaydetme etkisini elde etmez. Bellek alanını yeniden kullandığı için, havuza bellek alanı dönerken, koleksiyondaki öğeleri işlemesi gerekir ve ClearMode adında bir enumeration sunar, bu durum şu şekilde tanımlanır:
Varsayılan olarak, varsayılan değer Auto kullanılabilir ve özel performans gereksinimleri varsa, riskleri bildikten sonra Never kullanabilirsiniz. Referans tipleri ve referans türleri içeren değer tipleri için, bellek alanını havuza döndürürken dizi referansını boşaltmamız gerekir; eğer boşaldırılmazsa, GC bu bellek alanını boşaltamaz (çünkü öğenin referansı her zaman havuzda tutulmuştur), eğer saf değer tipiyse, boşaltılamaz, bu makalede referans tipleri ile struktur (değer türü) dizileri arasındaki depolama farkını açıklıyorum; saf değer tiplerinde nesne başlığı geri dönüşümü yoktur ve GC müdahalesi gerekmez.
. .NET Performans Optimizasyonu - Struct alternatif sınıflarını kullanın:Bağlantı girişi görünür.
Performans karşılaştırması
Benchmark'ı tek yapmadım ve doğrudan kullandığım açık kaynak projelerin çalışma skoru sonuçları birçok projenin bellek kullanımı açısından 0 oldu, çünkü kullanılan havuzlu bellek ekstra tahsis yoktu.
PooledList<T>
Benchmark'ta set'e eklenen 2048 elemanları tekrar edin, . .NET yerel List için <T>110us (gerçek benchmark sonuçlarına göre, şekildeki milisaniyeler bir yazım hatası olmalı) ve 263KB bellek gerektirirken, PooledList <T>sadece 36us ve 0KB belleğe ihtiyaç duyar.
PooledDictionary<TKey, TValue>
Benchmark'ta sözlüğe 10_0000 eleman ekle, . .NET yerel Sözlük<TKey, TValue> 11ms ve 13MB bellek gerektirirken, PooledDictionary<TKey, TValue> yalnızca 7ms ve 0MB bellek gerektirir.
PooledSet<T>
Benchmark'ta hash koleksiyonunu döngüye çevirin, 10_0000 eleman ekleyin, . .NET yerel HashSet <T>5348ms ve 2MB gerektirirken, PooledSet <T>sadece 4723ms ve 0MB bellek gerektirir.
PooledStack<T>
Benchmark'ta yığını döngüye çevirerek 10_0000 eleman ekleyin, . .NET yerel PooledStack <T>1079ms ve 2MB gerektirirken, PooledStack <T>yalnızca 633ms ve 0MB bellek gerektirir.
PooledQueue<T>
Benchmark'ta döngüler arasında döngü yaparak kuyruğun içine 10_0000 eleman ekleyin, . .NET yerel <T>PooledQueue 681ms ve 1MB gerektirirken, PooledQueue <T>yalnızca 408ms ve 0MB bellek gerektirir.
Sahne manuel olarak yayınlanmıyor
Ayrıca, yukarıda havuzlu koleksiyon türünün serbest bırakılması gerektiğini söyledik, ancak serbest bırakılmasa da fark etmez, çünkü GC geri dönüştürülür.
Benchmark sonuçları aşağıdaki gibidir:
Sonuç yukarıdaki Benchmark sonuçlarından çıkarılabilir.
Havuzlu tip koleksiyonunu zamanında serbest bırakmak GC'yi zar zor tetikliyor ve bellek tahsis ediyor; yukarıdaki grafikten sadece 56 Bayt bellek ayrılıyor. Havuzlu tip koleksiyon serbest bırakılmasa bile, çünkü havuzdan bellek tahsis eder, ancak ReSize genişletme işlemi sırasında belleği tekrar kullanır ve GC ayırma belleği başlatma adımını atlar; bu nispeten hızlıdır. En yavaş olanı normal koleksiyon türünü kullanmaktır; her ReSize genişletme işlemi yeni bellek alanı için uygulanmalıdır ve GC ayrıca önceki bellek alanını geri kazanmalıdır.
İlke analizi
Önceki blog yazımı okuduysanız, koleksiyon türleri için başlangıç boyutunu belirlemeli ve C# Sözlüğü'nün uygulama ilkesini analiz etmelisiniz, .NET BCL geliştiricilerinin bu temel koleksiyon türlerinin temel veri yapılarını yüksek performanslı rastgele erişim için dizileri olarak kullandığını bilebilirsiniz, örnek olarak List'i <T>alalım.
Eklenen öğeleri depolamak için yeni bir dizi oluşturun. Dizide yeterli alan yoksa, genişletme işlemi iki kat daha büyük bir alan talep etmek için tetiklenir. Yapıcı kodu şöyledir ve doğrudan oluşturulmuş genel bir dizi olduğunu görebilirsiniz:
Yani havuzlu bellek istiyorsanız, sınıf kütüphanesinde yeni anahtar kelime uygulamasının kullanıldığı yeri havuzlu uygulamayı kullanmak için değiştirmeniz yeterlidir. Burada sizinle paylaşıyorum. NET BCL, ArrayPool olarak adlandırılan bir türdür; bu tür, GC üzerindeki baskıyı azaltmak ve sık dizi oluşturma ve yok etme durumunda performansı artırmak için kullanılabilir.
Havuzlu tipimizin temel katmanı, kaynak havuzlarını paylaşmak için ArrayPool kullanmaktır ve yapıcısından, varsayılan olarak ArrayPool kullandığını <T>görebiliyoruz. Array nesnelerini atamak için paylaşılıyor ve tabii ki kendi ArrayPool'unuzu da oluşturarak bunu kullanabilirsiniz.
Ayrıca, kapasite ayarlama işlemi (genişleme) yapılırken, eski dizi iş ipliği havuzuna geri döner ve yeni dizi de havuzdan alınır.
Ayrıca, yazar Span'ı kullanarak Ekle ve Ekle gibi API'leri optimize ederek onlara daha iyi rastgele erişim performansı sağlar. Ayrıca, TryXXX serisi API eklendi, böylece daha pratik bir şekilde kullanabiliyorsunuz. Örneğin, List <T>sınıfı <T>PooledList'e kıyasla 170'e kadar modifikasyona sahiptir.
özet
Gerçek çevrimiçi kullanımımızda, yerel koleksiyon tipini Pooled tarafından sağlanan koleksiyon tipiyle değiştirebiliriz; bu da bellek kullanımını ve P95 gecikmesini azaltmada çok faydalıdır. Ayrıca, yayınlamayı unutsanız bile, performans yerel koleksiyon türü kullanmaktan çok daha kötü olmaz. Tabii ki, en iyi alışkanlık zamanında serbest bırakmaktır.
Özgün:Bağlantı girişi görünür.
|
Önceki:RecyclableMemoryStream, yüksek performanslı .NET akışı sağlarÖnümüzdeki:[Pratik dövüş] Sunucu, ağ hızını test etmek için LibreSpeed geliştiriyor
|