.NET Performans & Bellek Yönetimi 4 dk okuma 11 okunma

.NET'te GC Basıncını Azaltma Teknikleri

GC basıncını azaltmanın özü, tahsis sayısını düşürmektir; GC ayarlarını kurcalamak değil. Sıcak yollarda ArrayPool<T>, Span<T>, stackalloc ve struct kullanımı, kısa ömürlü büyük nesnelerden (LOH) kaçınma ve StringBuilder tercihi en etkili adımlardır. Server GC ve DATAS gibi ayarlar yalnızca profilleme sonrası, darboğazın GC olduğu kanıtlandığında devreye alınmalıdır.

.NET'te GC Basıncını Azaltma Teknikleri

.NET'te GC Basıncını Azaltma Teknikleri

.NET'te performans darboğazlarının büyük çoğunluğu, doğrudan CPU veya veritabanından değil, Garbage Collector (GC) baskısından kaynaklanır. Her new çağrısı, GC'nin gelecekte yapacağı işi artırır; sık ve büyük tahsisler ise Gen0 koleksiyonlarını sıklaştırır, hatta Gen2 (tam) koleksiyonları tetikleyerek uygulamada gözle görülür duraklamalara yol açar. Bu yazıda, GC basıncını azaltmanın kanıtlanmış yollarını ve hangi durumda GC ayarlarına müdahale edilmesi gerektiğini ele alacağız.


1. Önce Ölç: GC Gerçekten Darboğaz mı?

GC ayarlarını değiştirmeden veya kodu optimize etmeye başlamadan önce, sorunun gerçekten GC olduğundan emin olmak gerekir. dotnet-counters, dotnet-trace, Visual Studio profiler veya PerfView ile şu metrikleri izleyin:

  • Gen0/Gen1/Gen2 koleksiyon sayıları: Gen2 koleksiyonları sık çalışıyorsa, GC ciddi bir yük oluşturuyor demektir.

  • Allocation rate (tahsis hızı): Saniyede ne kadar bellek tahsis ediliyor?

  • LOH (Large Object Heap) kullanımı: 85.000 byte üzeri nesneler LOH'a gider ve varsayılan olarak sıkıştırılmaz; sık LOH tahsisi, Gen2 koleksiyonlarını tetikler .

Altın Kural: "Tahsisleri azaltmak" neredeyse her zaman "GC'yi ayarlamaktan" daha etkilidir. GC ayarları, profilleme sonrası darboğazın GC olduğu kanıtlandığında ve tahsis azaltma yolları tükendiğinde düşünülmelidir .


2. En Etkili Teknik: Tahsisleri Azaltmak

A. ArrayPool<T> ile Buffer'ları Yeniden Kullanın

Büyük veya sık kullanılan byte/char buffer'ları her seferinde new ile oluşturmak, hem LOH baskısı yaratır hem de GC'yi sık çalıştırır. ArrayPool<T>.Shared ile buffer'ları havuzdan ödünç alıp geri bırakmak, bu baskıyı dramatik biçimde azaltır .

csharp

// Kötü: Her çağrıda yeni tahsis
byte[] buffer = new byte[8192];

// İyi: Havuzdan ödünç al, işin bitince geri bırak
byte[] buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
    // buffer üzerinde çalış
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

B. Span<T> ve stackalloc ile Kopyasız İşlem

String veya dizi dilimlerini kopyalamadan işlemek, hem tahsisi hem de GC yükünü ortadan kaldırır. Span<T>, dizi ve string'lerin dilimlerine sıfır tahsisle erişim sağlar; küçük ve sabit boyutlu geçici buffer'lar için stackalloc kullanılabilir .

csharp

// Kopyalama yapmadan string dilimi işleme
ReadOnlySpan<char> span = input.AsSpan();
var slice = span.Slice(0, 10); // Tahsis yok

C. struct ile Küçük Değerleri Stack'te Tutun

Küçük, kısa ömürlü veriler için class yerine struct (veya record struct) kullanmak, heap tahsisini ortadan kaldırır. Ancak büyük struct'lar metot parametresi olarak geçirildiğinde kopyalama maliyeti artar; in veya ref ile bu maliyet azaltılabilir .

D. Döngülerde LINQ ve String Birleştirmeden Kaçının

LINQ, okunabilirlik sağlar ancak sıcak döngülerde closure, iterator ve ara koleksiyon tahsisleri üretebilir. Benzer şekilde, döngü içinde + ile string birleştirmek her iterasyonda yeni string tahsis eder; StringBuilder veya string.Create tercih edilmelidir .

E. Boxing'den Kaçının

Değer tiplerini object veya non-generic arayüzlere dönüştürmek (boxing), heap'te ek tahsis yaratır. Generic koleksiyonlar (List<int> gibi) kullanmak bu tahsisi önler .


3. LOH (Large Object Heap) Yönetimi

85.000 byte üzeri nesneler LOH'a gider ve yalnızca Gen2 koleksiyonlarında temizlenir. Sık sık büyük ve geçici nesneler oluşturmak, gereksiz Gen2 koleksiyonlarına yol açar .

Çözümler:

  • Büyük nesneleri parçalara bölün: Tek büyük bir dizi yerine, ArrayPool ile yönetilen daha küçük parçalar kullanın.

  • GCSettings.LargeObjectHeapCompactionMode: LOH'u sıkıştırmak için kullanılabilir, ancak bu pahalı bir işlemdir ve yalnızca parçalanma ciddi boyutlara ulaştığında düşünülmelidir .


4. GC Ayarları: Ne Zaman ve Nasıl?

Tahsis azaltma yolları tükendikten sonra, GC davranışını iş yüküne göre ayarlamak gerekebilir.

Server GC vs. Workstation GC:

  • Server GC (<ServerGarbageCollection>true</ServerGarbageCollection>), çok çekirdekli sunucularda işlem hacmini (throughput) maksimize etmek için tasarlanmıştır ve ASP.NET Core uygulamalarında varsayılandır .

  • Workstation GC, tek çekirdekli veya düşük gecikme (low-latency) gerektiren istemci uygulamaları için uygundur.

DATAS (Dynamic Adaptation to Application Sizes):
.NET 9'dan itibaren Server GC'de varsayılan olarak etkindir. Tek bir heap ile başlar ve yüke göre heap sayısını büyütür/küçültür. Microsoft'un ölçümlerine göre, çalışma setinde %80'in üzerinde azalma sağlarken tepe RPS'de yalnızca %2-3 düşüşe neden olur. Bellek kısıtlı konteyner ortamlarında faydalıdır; ancak makineyi tek başına kullanan throughput odaklı servislerde, etkisini ölçmek için GarbageCollectionAdaptationMode: 0 ile kapatılıp karşılaştırılabilir .

Önemli: GC.Collect()'i üretimde asla manuel çağırmayın. Bu, GC'yi uygun olmayan bir zamanda tetikleyerek performansı ciddi şekilde düşürür; yalnızca bellek sızıntısı araştırırken teşhis amaçlı kullanılmalıdır .


5. En İyi Pratikler Özeti

Teknik Etki
ArrayPool<T> Büyük/sık buffer tahsislerini ortadan kaldırır
Span<T> / stackalloc Dilimleme ve geçici buffer'larda sıfır tahsis
struct (küçük veriler) Heap tahsisini stack'e taşır
StringBuilder Döngüde string birleştirme tahsisini önler
LOH'tan kaçınma Gereksiz Gen2 koleksiyonlarını önler
Server GC / DATAS Yalnızca profilleme sonrası, iş yüküne göre

Sonuç

GC basıncını azaltmanın özü, tahsis sayısını düşürmektir. ArrayPool<T>, Span<T>, stackalloc ve struct gibi modern .NET primitifleri, sıcak yollarda sıfıra yakın tahsisle çalışmanızı sağlar. LOH'a giden büyük ve geçici nesnelerden kaçınmak, Gen2 koleksiyonlarının en büyük tetikleyicisini ortadan kaldırır. GC ayarları (Server GC, DATAS) ise ancak profilleme sonrası, darboğazın gerçekten GC olduğu kanıtlandığında ve tahsis azaltma yolları tükendiğinde devreye alınmalıdır. Unutmayın: En iyi GC, hiç çalışmayan GC'dir; en iyi optimizasyon, ölçülmüş ve kanıtlanmış olandır.

Tüm yazılar Soru ya da yorumun mu var? Yaz