.NET PROFİLİNG NEDİR? CPU/MEMORY ANALİZİ VE PERFORMANS İYİLEŞTİRME
Uygulama “hızlı” görünürken kullanıcı şikâyetleri artıyor, sunucu maliyeti büyüyor ve bazı istekler ara ara zaman aşımına düşüyorsa, sorun çoğu zaman kodun hangi bölümünün ne kadar kaynak tükettiğini bilmemekten çıkar. İşte .NET profiling, bu belirsizliği ortadan kaldırarak CPU zamanını, bellek tahsislerini ve çalışma zamanı davranışlarını ölçülebilir hale getirir. “Nerede yavaşlıyoruz?” sorusuna tahmine değil, veriye dayalı cevap verir.
Profiling yalnızca “yavaş metotları bulmak” değildir. Aynı zamanda GC baskısı, gereksiz allocation, kilitlenme (lock contention), thread starvation, aşırı I/O bekleme ve yanlış asenkron tasarım gibi kök nedenleri görünür kılar. Böylece optimizasyon, rastgele mikro iyileştirmeler yerine, en yüksek etkiyi veren noktalara odaklanır.
Bu yazıda .NET profiling kavramını, CPU ve bellek analizinin nasıl yapıldığını, doğru araç seçimini ve performans iyileştirme sürecini uçtan uca ele alacağız. Gerektiğinde ölç–yorumla–değiştir–yeniden ölç döngüsünü kurarak hem stabil hem de sürdürülebilir sonuçlara ulaşmayı hedefleyeceğiz.

.NET profiling nedir ve neyi ölçer?
.NET profiling, uygulamanın çalışma anındaki davranışını sistematik biçimde gözlemleme ve ölçme yöntemidir. Temel amaç; hangi kod yollarının CPU zamanını tükettiğini, hangi operasyonların bellek tahsis ettiğini, GC’nin ne sıklıkla çalıştığını ve hangi bekleme noktalarının gecikme yarattığını ortaya çıkarmaktır. Bu ölçümler, yalnızca “en yavaş fonksiyon” listesi değildir; çağrı zincirlerini, frekansı ve bağlamı da kapsar.
CPU tarafında profil, çoğu zaman “süre” ve “çağrı sayısı” üzerinden okunur. Ancak gerçek dünyada “süre” tek başına yetmez: sıcak yolun (hot path) çok çağrılan ama kısa süren bir metot olması da mümkündür. Bellek tarafında ise allocation profili, hangi tiplerin hangi call stack üzerinden tahsis edildiğini gösterir. Allocation yoğunluğu yükseldikçe GC basıncı artar, latency dalgalanır ve throughput düşer.
Profiling türleri: sampling ve instrumentation
En yaygın iki yaklaşım sampling ve instrumentation’dır. Sampling, düzenli aralıklarla stack örnekleyerek “nerede zaman harcanıyor?” sorusuna düşük ek yükle cevap verir. Instrumentation ise metot giriş–çıkışlarını izleyerek daha detaylı ölçüm sunar; buna karşılık ek yük daha yüksek olabilir. Üretimde genellikle sampling, geliştirme ve derin analiz aşamasında ise ihtiyaca göre instrumentation tercih edilir.
Profiling’e başlamadan önce: hedef, metrik ve senaryo
Profiling çalışmasının başarısı, “ne arıyoruz?” sorusuna net yanıtla başlar. Sadece “yavaş” demek yerine ölçülebilir hedef belirleyin: örneğin p95 gecikmeyi 450 ms’den 250 ms’ye indirmek, CPU kullanımını %75’ten %55’e çekmek ya da istek başına allocation’ı 600 KB’dan 200 KB’a düşürmek gibi. Bu hedefler, hangi veri setini toplayacağınızı ve hangi araçla başlayacağınızı belirler.
Ardından senaryoyu sabitleyin. Aynı yük altında, aynı verilerle, aynı çevresel koşullarda çalışmak gerekir. Profiling sırasında rastgele koşulların değişmesi, yanlış çıkarımlara yol açar. Ayrıca ölçüm süresini de planlayın: kısa örnekler anlık dalgalanmaları kaçırabilir; çok uzun örnekler ise dosya boyutunu ve analiz maliyetini artırır.
“Önce ölç, sonra optimize” döngüsü
İyileştirme döngüsü basittir: ölç, analiz et, en yüksek etkiyi olan noktayı seç, değiştir, yeniden ölç. Değişiklikten sonra yalnızca toplam süreye bakmak yerine, ilgili metriklerin (allocation, GC sayısı, lock bekleme süresi, thread sayısı) beklenen yönde hareket edip etmediğini kontrol edin. Yan etkileri yakalamak için aynı senaryoyu tekrar çalıştırmak kritik önemdedir.
CPU profilleme: sıcak yolları (hot path) bulma
CPU profilleme, uygulamanın hangi çağrı zincirlerinde zaman harcadığını ortaya çıkarır. En sık görülen problemler; gereksiz döngüler, pahalı LINQ zincirleri, aşırı string işlemleri, yanlış caching, lock contention ve senkron I/O beklemeleridir. Profil çıktısını okurken, yalnızca “en üstteki metot”a değil, onu bu noktaya getiren çağrı zincirine bakmak gerekir.
Özellikle web uygulamalarında, CPU tüketimi çoğu zaman gerçek iş yükünden ziyade “çöp” işlerden gelir: gereksiz JSON serileştirme, çok büyük object graph’ları, tekrar eden mapping ve formatlama, her istek için yeni oluşturulan pahalı nesneler… Sampling profilleri bu noktaları hızla gösterir; flame graph’lar ise yoğunluğu görsel olarak anlamayı kolaylaştırır.
Lock contention ve thread starvation işaretleri
Eğer profilde sık sık monitor enter/exit, SemaphoreSlim.Wait, Task.Wait gibi bekleme kalıpları görüyorsanız, CPU problemi gibi görünen şey aslında bekleme problemi olabilir. Thread starvation, özellikle yanlış yapılandırılmış thread pool veya senkron bloklayan kod nedeniyle ortaya çıkar. Bu durumda çözüm, daha hızlı CPU değil; daha doğru eşzamanlılık modeli ve gereksiz kilitlerin kaldırılmasıdır.

Bellek profilleme: allocation, GC ve memory leak tespiti
Bellek profilleme iki ana soruya odaklanır: (1) Hangi kod yolları en çok allocation yapıyor? (2) Serbest kalması gereken nesneler neden yaşamaya devam ediyor? İlk soru allocation profiliyle, ikinci soru ise heap snapshot ve referans analiziyle cevaplanır. Sık GC çalışması, genellikle “çok allocation” veya “uzun yaşayan büyük nesneler” anlamına gelir.
.NET’te GC’nin kendisi “kötü” değildir; amaç GC’yi sıfırlamak değil, gereksiz GC baskısını azaltmaktır. Örneğin istek başına kısa ömürlü çok sayıda string üretmek, LOH’a düşen büyük byte[] buffer’lar, sürekli büyüyen cache’ler veya event handler’larla tutulan referanslar tipik sorunlardır. Allocation hotspot tespit edildiğinde, daha az nesne üretmek, pooling kullanmak ya da daha uygun veri yapısına geçmek hızlı sonuç verir.
Leak ile “beklenen büyüme”yi ayırmak
Her büyüme leak değildir. Örneğin cache’ler ısındıkça bellek artabilir ve sonra plato yapabilir. Leak ise zamanla sürekli artar ve GC, beklenen nesneleri toplayamaz. Snapshot karşılaştırması, hangi tiplerin sayısının ve toplam boyutunun kalıcı biçimde yükseldiğini gösterir. Referans zinciri analiziyle bu nesneleri hayatta tutan kökleri (static alanlar, singleton’lar, event abonelikleri) bulabilirsiniz.
.NET’te yaygın profiling araçları ve ne zaman hangisi?
Araç seçimi, hedefe ve ortama göre değişir. Geliştirme ortamında detaylı UI’lı profiller hız kazandırır. Üretimde ise düşük ek yükle izleme yapan komut satırı araçları ve çalışma zamanı izleme mekanizmaları öne çıkar. Ayrıca uygulamanın .NET sürümü, OS, container kullanımı ve izinler de araç seçimini etkiler.
- dotnet-trace / EventPipe: Üretimde veya container içinde düşük ek yükle iz alma, sonra offline analiz.
- PerfView: ETW/trace analizinde güçlü; GC, CPU, thread olayları için derin görünürlük.
- JetBrains dotTrace / dotMemory: UI ile hızlı yön bulma; call tree ve snapshot analizi.
- Visual Studio Profiler: IDE içinde CPU/Memory analizine hızlı başlangıç.
- BenchmarkDotNet: Mikro benchmark ve regresyon takibi; değişikliklerin etkisini ölçme.
Üretimde veri toplarken dikkat edilmesi gerekenler
Üretimde profiling, “sistemi daha da yavaşlatmamalı” prensibiyle yürütülür. Mümkünse sampling tabanlı ve kısa süreli trace alın. Kişisel veri içeren payload’ları loglamayın; izleme kapsamını daraltın. Ayrıca olay yoğunluğunu kontrol etmek için filtreleme ve örnekleme stratejileri kullanın. En iyi profil, sorun anını yakalayan ama sistemi riske atmayan profildir.
Pratik: dotnet-trace ile CPU ve GC izini alma
dotnet-trace, EventPipe üzerinden süreçten iz toplayarak CPU, GC ve runtime olaylarını kaydetmenizi sağlar. Özellikle container ortamında, UI’lı araçları çalıştırmak zor olduğunda çok kullanışlıdır. Tipik akış; süreç ID’sini bulmak, kısa bir süre iz almak ve sonra dosyayı PerfView gibi bir araçla analiz etmektir.
# Süreçleri listele
dotnet-trace ps
# Belirli bir süreçten 30 saniyelik iz al (örnek)
dotnet-trace collect --process-id 12345 --duration 00:00:30 --providers Microsoft-DotNETCore-SampleProfiler,Microsoft-Windows-DotNETRuntime:0x1C000080018:5
# Çıktı dosyası genellikle .nettrace olur; offline analiz için kopyalayın
Toplanan izde CPU açısından hot path’leri, GC açısından ise gen2 koleksiyonları, pause sürelerini ve allocation basıncını takip edin. Eğer kısa izlerde bile sık gen2 veya uzun GC pause’ları görüyorsanız, allocation profiline inmek gerekir. Bu aşamada PerfView veya benzeri bir analiz aracıyla stack’lerde hangi yolların allocation ürettiğini bulmak mümkün olur.
İzleri yorumlarken sık yapılan hatalar
Bir metot üst sırada göründüğünde hemen “orayı optimize edelim” demek yanıltıcı olabilir. Bazı metotlar framework seviyesinde “çatı” görevindedir ve üstte çıkar. Önemli olan, o metodu tetikleyen uygulama kodu ve veri akışıdır. Ayrıca tek bir ölçümle karar vermeyin; aynı senaryoda birkaç tekrar yapıp benzer desenlerin oluştuğunu doğrulayın.
Pratik: allocation’ı azaltma ve BenchmarkDotNet ile doğrulama
Bellek baskısını azaltmanın en etkili yollarından biri, gereksiz allocation’ları düşürmektir. Örneğin çok kullanılan bir kod yolunda her çağrıda yeni liste üretmek yerine, uygun bir yapı kullanmak veya pooling tercih etmek ciddi fark yaratabilir. Aşağıdaki örnekte, string birleştirme yaklaşımının allocation etkisini karşılaştırmak için BenchmarkDotNet kullanıyoruz.
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class StringConcatBench
{
private readonly string[] _items = new[] { "alpha", "beta", "gamma", "delta", "epsilon" };
[Benchmark]
public string PlusConcat()
{
var s = "";
foreach (var it in _items)
s += it + ",";
return s;
}
[Benchmark]
public string StringBuilderConcat()
{
var sb = new StringBuilder(64);
foreach (var it in _items)
sb.Append(it).Append(',');
return sb.ToString();
}
}
public class Program
{
public static void Main(string[] args)
=> BenchmarkRunner.Run<StringConcatBench>();
}
Bu tarz bir benchmark, “hissiyat” yerine ölçüm sunar: süre kadar, allocation miktarı da raporlanır. Eğer üretim senaryosunda kritik bir hot path’te benzer bir pattern varsa, küçük bir refactor hem CPU’yu hem de GC baskısını düşürebilir. Burada hedef, her yerde StringBuilder kullanmak değil; sıcak yolda gereksiz nesne üretimini azaltmaktır.
Allocation kaynaklarını sınıflandırma
Allocation’ları üç sınıfta düşünmek faydalıdır: zorunlu allocation (iş gereği), gereksiz allocation (kolayca kaldırılabilir) ve tasarım kaynaklı allocation (kapsamlı refactor gerektirir). İlk iki sınıf genellikle hızlı kazanç sağlar. Üçüncü sınıfta ise API tasarımı, veri modeli ve akışın sadeleştirilmesi gibi daha stratejik değişiklikler gerekir.

Performans iyileştirme stratejisi: önce büyük kazançlar
Profiling sonucu elde ettiğiniz bulguları bir “iyileştirme backlog”una dönüştürün. Her madde için beklenen etki (latency/throughput), risk, uygulama süresi ve doğrulama yöntemi belirleyin. En hızlı kazanımlar çoğu zaman şunlardan gelir: gereksiz allocation azaltma, pahalı seri hale getirme işlemlerini iyileştirme, doğru caching, lock’ları azaltma ve yanlış senkron beklemeleri kaldırma.
İyileştirme yaparken “mikro optimizasyon” tuzağına düşmeyin. Örneğin bir metotta 2 ms kazanmak, eğer o metot nadiren çağrılıyorsa toplam etkiye dönüşmeyebilir. Aksine, istek başına 50 KB allocation azaltmak, GC baskısını düşürerek sistem genelinde daha stabil p95 değerleri sağlayabilir. Toplam etkiyi, hedef metriklere göre takip edin.
Regresyonu önlemek: otomatik ölçüm ve izleme
Profiling bulgularını kalıcı hale getirmek için ölçümü otomatikleştirin. Kritik kod yolları için mikro benchmark’lar ekleyin, servis seviyesinde load test senaryolarını standartlaştırın ve prod gözlemlerini (CPU, GC, p95) dashboard’da izleyin. Böylece bir sonraki sürümde performansın geri gitmesi erken yakalanır. Derinleşmek isterseniz .NET test ve performans eğitimi kapsamında profiling, ölçüm tasarımı ve pratik laboratuvar senaryolarını sistemli biçimde çalışabilirsiniz.
Özet: .NET profiling ile veriye dayalı iyileştirme
.NET profiling; CPU ve bellek tüketimini görünür kılarak, performans sorunlarını “tahmin” yerine “kanıt” ile çözmenizi sağlar. Sampling ve instrumentation gibi yaklaşımlar, doğru senaryo ve metriklerle birleştiğinde, en pahalı yolları hızlıca ortaya çıkarır. CPU profilleme hot path’leri, bellek profilleme ise allocation ve leak kaynaklarını işaret eder. Son adımda ise değişiklikleri BenchmarkDotNet veya tekrar eden senaryolarla doğrulayarak sürdürülebilir bir performans kültürü kurabilirsiniz.


