C# Benchmarking Kullanımı (BenchmarkDotNet)
C#’ta BenchmarkDotNet ile benchmarking yapmayı öğrenin. Performans ölçümü ve kod optimizasyonu için pratik örnekler.
Performans ölçümü (benchmarking), iki veya daha fazla yaklaşımın hız, bellek tüketimi ve ölçeklenebilirlik açısından karşılaştırılmasıdır. .NET dünyasında bu iş için BenchmarkDotNet endüstri standardıdır. Derlemeyi optimize eder, ısınma (warmup) yapar, çoklu tekrarlar çalıştırır, istatistiksel analiz (ortalama, medyan, sapma) üretir ve raporlar. Bu makalede BenchmarkDotNet'in kurulumu, temel kullanımı ve gerçek hayata yönelik püf noktalarını göreceksiniz.
Kurulum
BenchmarkDotNet paketini test/benchmark projenize ekleyin:
dotnet new console -n BenchmarkOrnek
cd BenchmarkOrnek
dotnet add package BenchmarkDotNet
Programı Release modda çalıştırmanız önerilir; BDN bunu otomatik yönetir fakat proje yapılandırmanız güncel olmalıdır.
İlk Benchmark: Basit Karşılaştırma
Aşağıdaki örnek, iki dizge birleştirme yöntemini karşılaştırır.
Her benchmark metodu [Benchmark] ile işaretlenir; çalıştırıcı BenchmarkRunner.Run<T>() ile çağrılır.
using System;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
public class StringBenchmarks
{
private const int N = 1000;
[Benchmark]
public string PlusOperator()
{
string s = string.Empty;
for (int i = 0; i < N; i++)
s += "x";
return s;
}
[Benchmark]
public string StringBuilderAppend()
{
var sb = new StringBuilder();
for (int i = 0; i < N; i++)
sb.Append('x');
return sb.ToString();
}
}
class Program
{
static void Main() => BenchmarkRunner.Run<StringBenchmarks>();
}
Çalıştırıldığında BenchmarkDotNet.Artifacts altında detaylı raporlar (.md, .csv, .html) üretir.
Temel Öznitelikler (Attributes)
[Benchmark]: Ölçülecek metot.[GlobalSetup]/[GlobalCleanup]: Tüm ölçümlerden önce/sonra bir kez çalışır (ör. veri hazırlama).[IterationSetup]/[IterationCleanup]: Her yinelemeden önce/sonra çalışır.[Params(...)]: Aynı benchmark'ı farklı parametre değerleriyle çalıştırır.[MemoryDiagnoser]: Bellek (Allocated B, Gen0/1/2) metriklerini ekler.
using BenchmarkDotNet.Attributes;
[MemoryDiagnoser]
public class SearchBench
{
private int[] _data;
[Params(1_000, 100_000)]
public int Size;
[GlobalSetup]
public void Setup()
{
var rnd = new Random(42);
_data = new int[Size];
for (int i = 0; i < Size; i++) _data[i] = rnd.Next();
Array.Sort(_data);
}
[Benchmark(Baseline = true)]
public bool LinearSearch() => Array.Exists(_data, x => x == 42);
[Benchmark]
public bool BinarySearch() => Array.BinarySearch(_data, 42) >= 0;
}
Baseline = true seçeneği, sonuç tablosunda oran (Ratio) hesaplanmasına olanak verir.
Jobs, Runtime ve Ortam Ayarları
Job yapılandırmalarıyla hedef çalışma zamanı, JIT, platform, GC modu gibi ayarlar belirlenebilir.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net472)]
[MemoryDiagnoser]
public class HashBench
{
[Benchmark] public int DefaultHash() => "abcdef".GetHashCode();
}
class Program
{
static void Main() => BenchmarkRunner.Run<HashBench>();
}
Bu yapı tek seferde birden fazla hedef runtime üzerinde kıyaslama yapar (ör. .NET 8.0 vs .NET Framework 4.7.2).
Warmup, Iteration ve Outlier Yönetimi
BenchmarkDotNet, gereksiz gürültüyü azaltmak için otomatik ısınma ve çok sayıda ölçüm iterasyonu yapar. İsterseniz manuel ayarlayabilirsiniz:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
[Config(typeof(MyConfig))]
public class MathBench
{
private class MyConfig : ManualConfig
{
public MyConfig()
{
AddColumn(BenchmarkDotNet.Columns.StatisticColumn.Mean,
BenchmarkDotNet.Columns.StatisticColumn.StdDev);
AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);
AddJob(BenchmarkDotNet.Jobs.Job.Default
.WithWarmupCount(3)
.WithIterationCount(10));
}
}
[Benchmark] public double Sqrt() => Math.Sqrt(12345.6789);
}
İstatistiklerde Mean, Median, StdDev, Min, Max gibi değerler yer alır; aykırı değerler filtrelenebilir.
Parametrik Karşılaştırmalar
[Params] ile farklı veri boyutlarını veya stratejileri tek tabloda görebilirsiniz.
using BenchmarkDotNet.Attributes;
using System.Linq;
[MemoryDiagnoser]
public class SumBench
{
[Params(10, 1000, 100000)]
public int N;
private int[] _arr;
[GlobalSetup]
public void Setup() => _arr = Enumerable.Range(1, N).ToArray();
[Benchmark(Baseline = true)]
public long ForLoop()
{
long s = 0;
for (int i = 0; i < _arr.Length; i++) s += _arr[i];
return s;
}
[Benchmark]
public long LinqSum() => _arr.Sum(x => (long)x);
}
Sonuçlar, N değerine göre değişimi görmenizi sağlar; mikro-optimizasyon mu yoksa algoritmik fark mı, kolayca anlaşılır.
İleri Teşhis: Disassembly ve Donanım Sayaçları
- DisassemblyDiagnoser: JIT tarafından üretilen assembly'i gösterir (ileri seviye analiz).
- HardwareCounters: CPU sayaçları (CacheMisses, BranchMispredictions vb. — platform desteğine bağlı).
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
[DisassemblyDiagnoser(printSource: true, maxDepth: 2)]
public class CryptoBench
{
[Benchmark] public byte[] GuidNew() => Guid.NewGuid().ToByteArray();
}
Bu teşhisler, sıcak kod yollarındaki gereksiz tahsisleri ve talimat seviyesindeki farkları görmede çok faydalıdır.
Çıktıyı Dışa Aktarma (Exporters)
Sonuçlar Markdown/CSV/HTML olarak dışa alınabilir. CI/CD raporlaması için idealdir.
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
public class CsvConfig : ManualConfig
{
public CsvConfig()
{
AddExporter(CsvExporter.Default);
AddLogger(ConsoleLogger.Default);
}
}
Sık Yapılan Hatalar ve En İyi Uygulamalar
- Stopwatch ile karşılaştırmayın: Mikro benchmark’ta gürültü yüksektir, JIT/GC etkisi ölçümü bozar.
- Release + x64: Debug derlemeleri yanıltıcıdır; JIT optimizasyonları olmaz.
- Yan etkileri engelleyin: Benchmark metodu sonucu kullanmıyorsa JIT eliyebilir. Sonucu döndürün veya
Consumerkullanın. - Isınma şart: İlk çalıştırmalarda JIT ve cache etkisi yüksektir; BDN bunu otomatik halleder.
- Gerçekçi veri setleri: Yapay küçük veriler yanlış sonuca götürebilir.
- Tek makine, sabit ortam: Paralelde arka plan işler, antivirüs vb. gürültü yapar.
Gerçek Hayat Örneği: JSON Serileştirme Karşılaştırması
Aşağıdaki örnekte farklı JSON kütüphaneleriyle (örnek amaçlı) serileştirme karşılaştırılır ve bellek tahsisi ölçülür. Hangi kütüphanenin sizin veri modeliniz için daha hızlı ve az tahsis yaptığını net biçimde görebilirsiniz.
using System;
using System.Collections.Generic;
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
// Ek: Newtonsoft.Json kullanıyorsanız paketi ekleyip uncomment edebilirsiniz
// using Newtonsoft.Json;
[MemoryDiagnoser]
public class JsonBench
{
private List<Kisi> _list;
[Params(100, 10_000)]
public int Count;
[GlobalSetup]
public void Setup()
{
_list = new List<Kisi>(Count);
for (int i = 0; i < Count; i++)
_list.Add(new Kisi { Ad = "Ada", Soyad = "Lovelace", Yas = 28, Aktif = (i % 2) == 0 });
}
[Benchmark(Baseline = true)]
public string SystemTextJson() => JsonSerializer.Serialize(_list);
// [Benchmark]
// public string NewtonsoftJson() => JsonConvert.SerializeObject(_list);
}
public class Kisi
{
public string Ad { get; set; }
public string Soyad { get; set; }
public int Yas { get; set; }
public bool Aktif { get; set; }
}
class Program
{
static void Main() => BenchmarkRunner.Run<JsonBench>();
}
Rapor, her yöntem için Mean, Error, StdDev, Allocated gibi metrikleri tek tabloda verir. Allocated değerinin düşük olması GC baskısını azaltır ve yoğun iş yüklerinde büyük fark yaratır.
CI/CD Entegrasyonu ve Tekrarlanabilirlik
- Pipeline’da benchmark’ları ayrı bir aşamada koşun; sonuçları
.csvveya.mdolarak arşivleyin. - Sabit donanım/VM kullanın; CPU güç planını “Yüksek Performans” seçin.
- Karşılaştırmalı PR’lerde baseline işaretleyerek regresyonları yakalayın.
TL;DR
- BenchmarkDotNet, güvenilir ve tekrarlanabilir .NET performans ölçümleri için standarttır.
[Benchmark],[Params],[MemoryDiagnoser]temel taşlardır.- Release/x64 ve gerçekçi veriyle ölçüm yapın; sonucu mutlaka kullanın.
- Jobs ile birden çok runtime ve GC ayarı karşılaştırın.
- Raporları CSV/MD/HTML dışa aktararak CI/CD’de izleyin; baseline ile regresyonları yakalayın.
İlişkili Makaleler
C# Memory Management ve Garbage Collector
C#’ta memory management ve garbage collector yapısını öğrenin. Bellek yaşam döngüsü, tahsis ve temizlik süreçleri anlatılıyor.
C# Span<T> ve Memory<T> ile Performans Optimizasyonu
C#’ta Span<T> ve Memory<T> ile performans optimizasyonunu öğrenin. Bellek yönetimi ve yüksek performanslı veri işlemleri örneklerle.
C# Task Parallel Library (TPL) ve Paralel Programlama
C#’ta Task Parallel Library ve paralel programlamayı öğrenin. Task, Parallel ve eşzamanlı işlem senaryoları örneklerle anlatılıyor.