Web uygulamalarında performansa dair...-2-

Bir .NET uygulamasında performanstan bahsederken ilk konuşulması gereken şey, uygulamanın hafıza kullanım miktarıdır. .NET uygulamalarında - ve özellikle de ASP.NET uygulamalarında da - yanlış hafıza yönetimi ve/veya kullanımı performansı direkt olarak olumsuz etkileyecektir.

.NET uygulamalarında hafıza yönetimi işini (yani nesneler için hafızada yer ayrılması ve sonra temizlenmesi) Garbage Collector (GC) mekanizması yapar. GC'nin faklı çalışma şekilleri vardır. Normalde hangi modun kullanılacağına CLR otomatik olarak karar verir. ASP.NET uygulamaları genellikle çok CPU'lu makinelerinde çalışacağından, "server GC" kullanırlar. Bunun anlamı, en basit şekliyle, şudur:

Uygulama çalışmaya başladığında mantıksal (logical) CPU sayısı kadar "thread", GC'ye ayrılır. Bu thread'ler GC dışında bir iş yapmazlar. GC devreye girmeye karar verdiğinde aktif tüm istekler, tüm işler askıya alınır ve bu thread'ler hafızadaki nesneleri taramaya ve temizlemeye başlarlar. Bu iş boyunca yeni gelen istekler de kuyruğa düşerler. Yani uygulamamız tamamen durur.

Sırf bu tanıma bakarak bile şu sonucu çıkarabiliriz: GC ne kadar seyrek devreye girerse ve devreye girdiğinde ne kadar kısa süre çalışırsa, uygulamanın genel performansına olumsuz etkisi o kadar az olacaktır. Ancak asıl sorun da tam olarak burada karşımıza çıkıyor: Bunu nasıl sağlayacağız?

.NET uygulamalarında, manuel olarak tetiklemek dışında GC'ye müdahale edemiyoruz. Manuel tetiklemenizi de, çok istisnai senaryolar dışında - kesinlikle önermiyoruz. GC sayısını azaltmanın ve kısa sürmesini sağlamanın tek yolu, uygulamanın genel hafıza kullanımını düşürmektir. Ancak bu, özellikle de ASP.NET uygulamalarında çok da kolay bir iş değildir.

  • İlk dikkat etmemiz gereken nokta "string" birleştirme (concatination) işleridir. Daha önce bir blogumda bahsettiğim gibi aşırı miktarda "string concatination" yapmak memory kullanımını olumsuz etkileyecektir. Bununla ilgili çok sayıda sorunla karşılaştık.
  • Bir başka nokta, aşırı büyük DataTable/DataSet nesneleri yaratmamak, veritabanından sadece ihtiyacımız olduğu kadar veri çekmektir.
  • Session'larda tutulan nesneler potansiyel olarak uzun süre hafızada kalacaklarından bunların ebatları ve adetlerine de dikkat etmenizi öneririm.
  • Ebatı 85000 byte'tan büyük nesneler diğerlerinde farklı bir "heap"te tutulur  ve GC onlara farklı davranır. Bu nedenle bu büyüklükteki nesnelerin sayısının olabildiğince az olması gerekir.
  • "Viewstate" nesnelerinin ebatı oldukça büyüyebilmektedir. Bunları kontrol altında tutmak, ve ihtiyaç olmayan kontroller için kapatmak da fayda sağlayacaktir.
  • Veritabanı başlantı nesneleri gibi "Close" veya "Dispose" metoduna sahip nesnelerin işleri biter bitmez kapatılması gerekir. Böylece mümkün oduğunca kısa süre hafızada kalmış olurlar.

Tüm bunlara ve daha fazlasına, uygulamanızın yapısına ve ihtiyaçlarına göre sizin karar vermeniz gerekmektedir.

Performance monitor

Aşağıdaki performans sayaçları hem hafıza kullanımımız hakkında fikir verecektir, hem de GC'nin genel performansımıza etkisini gösterecektir (tüm sayaçlar ".NET CLR Memory" altındadır):

  • # Bytes in all Heaps: Uygulama üzerindeki tüm .NET nesnelerinin toplam ebatını gösterir.
  • # Gen 0, 1, 2 Collections: Bu sayaçlar, her bir "generation" için GC'nin toplam kaç defa devreye girdiğini gösterir. Burada göreceğimiz rakamlardan ziyade artış hızı önemli olacaktır. Bununla ilgili net bir rakam vermemiz mümkün değildir. Ancak, örneğin, saniyede onlarca defa GC devreye giriyorsa, bunun performansa etkisinin çok olumsuz olacağını rahatlıkla söyleyebiliriz. İşte tam da bunları azaltmanın yolu yukarıda bahsettiklerimizdir.
    Bu sayaçlarla ilgili bir diğer önemli nokta da, Gen 0, 1 ve 2 arasındaki orandır. İdeal olan aralarında sırasıyla 100:10:1 gibi bir oran olmasıdır. Elbette gerçekte bu neredeyse hiç bir zaman mümkün olmayacaktır. Ama yine de birbirlerine çok yakın değerler görüyorsak hafıza kullanımızda sorunlar/sıkışmalar yaşadığımız açıktır.
  • # Induced GC: Manuel olarak tetiklenen GC adedini gösterir. Bunun, yukarıda da bahsettiğim gibi, sıfır olmasını bekliyoruz.

Kaynaklar

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework
https://msdn.microsoft.com/en-us/magazine/bb985010.aspx

Automatic Memory Management
https://msdn.microsoft.com/en-us/library/f144e03t.aspx

<gcServer> Element
https://msdn.microsoft.com/en-us/library/ms229357.aspx

<gcConcurrent> Element
https://msdn.microsoft.com/en-us/library/yhwwzef8.aspx