BENCHMARKDOTNET NEDİR?
Bir kod parçasının ne kadar sürdüğünü merak edip etrafına bir Stopwatch sarmak yazılım geliştirmenin neredeyse refleksif hareketidir. Sonuca bakarsın, kararı verirsin, devam edersin. Sonra biri aynı kodu farklı makinede çalıştırır, süre tamamen değişir. Hatta aynı makinede ikinci ölçümde bile fark çıkar. Soğuk başlangıç, JIT derlemesi, çöp toplayıcının araya girmesi, işletim sistemi zamanlaması — hepsi sonuca karışır. Mikro ölçekli karşılaştırmalarda Stopwatch'a güvenmek, terazide elini de tutarken tartı yapmaya benzer.
BenchmarkDotNet tam olarak bu kaygan zemini disipline eden bir .NET kütüphanesidir. Karşılaştırılacak metotları işaretlersin, çalıştırırsın; kütüphane warmup turlarını, tekrarlı çalıştırmayı, istatistiksel analizi, izole process'te yürütmeyi ve raporlamayı senin yerine yapar. Çıktıda artık "iki saniye sürdü" değil; "metot A ortalama 12.4 ns, B ortalama 47.1 ns, sapma şu kadar" gibi karşılaştırılabilir sayılar görürsün.
Stopwatch Neden Tek Başına Yetmiyor
Manuel ölçümde birden fazla görünmez değişken sonucu kirletir. İlk çalıştırmada JIT henüz metodu derlememiştir, bu yüzden ilk tur diğerlerinden kat be kat yavaş olur. Çöp toplayıcı (GC) ölçüm sırasında devreye girerse aniden milisaniyeler eklenir. CPU başka bir thread için frekansı düşürmüş olabilir. Termal throttling, OS scheduler, hatta antivirüs taraması bile etkiler.
Tek bir Stopwatch.Start / Stop bunlardan hiçbirini ayıklamaz. İki yöntemi yan yana koyup "bu daha hızlıymış" demek için aslında onlarca tur çalıştırıp ortalamayı, sapmayı, aykırı değerleri çıkarmak gerekir. BenchmarkDotNet bu sürecin tamamını otomatize eder.
Kütüphanenin Kutusundan Çıkanlar
BenchmarkDotNet, .NET Foundation altında geliştirilen açık kaynak bir projedir. NuGet üzerinden eklenir ve genellikle ayrı bir Console Application projesinde tutulur. Asıl uygulamadan izole çalıştırılır çünkü kütüphane benchmark'ı kendi başlattığı bir alt-process içinde, optimize edilmiş Release modunda yürütür.
Tipik bir kurulum şöyle ilerler:
dotnet add package BenchmarkDotNetile paket eklenir- Karşılaştırılacak metotları içeren bir sınıf yazılır
- Her metoda
[Benchmark]attribute'u koyulur - Program.cs'te
BenchmarkRunner.Run<SinifAdi>()çağrısı yapılır - Proje Release modunda çalıştırılır
Bu noktadan sonra kütüphane sırasıyla pilot turu (kaç iterasyon gerektiğini tahmin etme), warmup (JIT'in metotu derlemesini ve cache'in ısınmasını bekleme), gerçek ölçüm ve istatistiksel analiz aşamalarını yürütür. Sonuç hem ekrana hem markdown/CSV/HTML olarak dosyaya yazılır. Projenin resmi belgelerinde tüm attribute'lar ve gelişmiş senaryolar örneklerle anlatılır.

İlk Karşılaştırma: Benchmark Attribute'u
Klasik bir örnek olarak string birleştirme yöntemlerini ele alalım. Beş elemanlı bir diziyi += operatörü ile mi yoksa StringBuilder ile mi birleştirmek daha hızlıdır?
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class StringConcat
{
private readonly string[] _items = { "a", "b", "c", "d", "e" };
[Benchmark]
public string PlusOperator()
{
string result = "";
foreach (var item in _items)
result += item;
return result;
}
[Benchmark]
public string WithBuilder()
{
var sb = new System.Text.StringBuilder();
foreach (var item in _items)
sb.Append(item);
return sb.ToString();
}
}
class Program
{
static void Main() => BenchmarkRunner.Run<StringConcat>();
}Bu kadarı yeter. dotnet run -c Release komutuyla çalıştırıldığında kütüphane iki metodu kıyaslar ve sonucu tablo halinde verir. Beş elemanlı küçük girdide aslında += operatörü StringBuilder'dan daha hızlı çıkar — çoğu insanın beklediğinin tersi. Çünkü kurulum maliyeti küçük girdide ağır basar. İşte BenchmarkDotNet'in kıymeti tam burada görünür: önyargıları sayıyla yıkar.
Aynı Metodu Farklı Parametrelerle Sınamak
Yukarıdaki örnek beş elemanlık bir diziyi ölçüyor. Peki dizi 10, 100, 1000 elemanlı olsa karşılaştırma değişir mi? Burada [Params] attribute'u devreye girer. Bir alana yerleştirilen bu attribute, BenchmarkDotNet'in her parametre değeri için ayrı bir ölçüm turu yürütmesini sağlar.
public class StringConcat
{
[Params(5, 100, 1000)]
public int N;
private string[] _items;
[GlobalSetup]
public void Setup()
{
_items = new string[N];
for (int i = 0; i < N; i++) _items[i] = "x";
}
[Benchmark]
public string PlusOperator() { /* ... */ }
[Benchmark]
public string WithBuilder() { /* ... */ }
}Sonuçta her metot her N değeri için ayrı satır olarak raporlanır. Böylece StringBuilder'ın hangi eşikten sonra üstün geldiğini somut görürsün. [GlobalSetup] her tur başına bir kez çalışır; her ölçüm iterasyonundan önce çalışmasını istersen [IterationSetup] kullanılır.
Bellek Tahsisinin Ölçülmesi
Hız tek başına yetmez. Bir metot hızlı olabilir ama her çağrıda yığına 50 KB ayırıyorsa yüksek yüklü serviste GC baskısı yaratır. BenchmarkDotNet için [MemoryDiagnoser] attribute'unu sınıfa eklediğinde rapor tablosuna üç sütun daha gelir:
- Gen0 / Gen1 / Gen2: Her bin operasyon için gerçekleşen GC çağrı sayısı (kuşak bazlı)
- Allocated: Operasyon başına yığında ayrılan bayt miktarı
İki yöntem aynı hızda görünüyor ama biri 0 bayt ayırıyor, diğeri her çağrıda 240 bayt ayırıyorsa tercih netleşir. Bu görünmez maliyeti yakalamak BenchmarkDotNet'in en değerli kazanımlarından biridir; çünkü .NET geliştiricilerin sıkça karşılaştığı performans sorunlarının önemli bölümü hız değil allocation kaynaklıdır.

Çıktı Tablosunu Doğru Okumak
Konsola düşen tablo ilk bakışta yoğun görünür. Önemli sütunlar şunlardır:
- Mean: Tüm geçerli iterasyonların aritmetik ortalaması — birincil hız göstergesi
- Error: Ortalamanın güven aralığı (%99.9). Mean ± Error
- StdDev: Standart sapma. Yüksek değer ölçümde dalgalanma olduğunu söyler
- Median: Aykırı değerlerden etkilenmemiş orta nokta. Mean ile arasında ciddi fark varsa ölçümde gürültü vardır
- Ratio:
[Baseline = true]ile işaretlediğin metoda oran. "2.31x" yorum yapmayı kolaylaştırır
İki metot karşılaştırılıyorsa sadece Mean'e bakmak yanıltıcıdır. StdDev birinde çok yüksekse o yöntem ortalamada hızlı görünse bile gerçek dünyada öngörülemez davranır. Allocated sıfır olan yöntem uzun vadede çoğu zaman daha sağlıklıdır.
BenchmarkDotNet Hangi Durumlarda Yetmez
Kütüphane mikro-benchmark için tasarlanmıştır: nanosaniye-mikrosaniye aralığında metot karşılaştırması. End-to-end senaryo testi, HTTP servisi yük testi veya veritabanı sorgu zamanlaması için doğru araç değildir. Bu tip ölçümlerde k6, JMeter, Gatling gibi yük testi araçları veya dotnet-counters, PerfView gibi profil araçları daha uygundur.
Aynı şekilde async metotlar ölçülebilir ama dikkat gerekir; await edilen network çağrısının süresi BenchmarkDotNet'in kontrolündeki determinizmin dışındadır. Bu durumda kütüphane sana metodun overhead'ini söyler, sistemin tamamının davranışını değil. Pratik senaryolarla benchmarking ve .NET performans optimizasyonunu derinlemesine işleyen .NET test ve performans eğitimi bu sınırları örnek vakalarla ayırır.
Doğru aracı doğru ölçek için kullanmak, performans çalışmasının en az kod yazmak kadar önemli kısmıdır. BenchmarkDotNet "şu metot şundan ne kadar hızlı" sorusunun cevabını verir; "bu sistem dakikada kaç istek kaldırır" sorusunun değil.



