Özlem Erden
VakıfBank Teknoloji
4 min readNov 14, 2021

--

Kod Performans ve Memory-BenchmarkDotNet

Bir uygulama geliştirken şüphesiz en önemli özelliklerinden biri uygulamanın performansıdır. Özellikle bankacılık işlemlerinde süre uzamalarının yaşanmaması için kod kalitesi büyük önem arz etmektedir.

BenchmarkDotNet Nedir?

BenchmarkDotNet, methodlarımızı karşılaştırmaya yarayan, performanslarını izleyebilen ve ardından yakalanan performans verilerine ilişkin öngörüler sağlayabilen açık kaynak kodlu .NET kütüphanesidir.

Biz yazılımcılar bir uygulama yazarken çoğu zaman hangi kod parçasının daha hızlı çalıştığını bilmek zorundayız. Çünkü bir projede en önemli kısım daha hızlı ve performanslı çalışmasıdır. Ne kadar büyük proje olsa da performansı iyi değilse kötü bir ürün ortaya çıktığını gösterir. Bu sebeple Benchmark ile kıyaslama yaptığımız zaman performansa dayalı olarak doğru karar verip daha kaliteli ürün üretebiliyoruz.

Yazdığımız methodlarda süre uzaması olduğu zaman genellikle performans sorunu tespit etmek için kodlarımızın arasına Stopwatch’lar koyarak süreleri ve memory’i ölçmeye çalışırız. Bu durumda yazdığımız kod yapısı kirlenir ve düzeltmekte zorlanırız. BenchmarkDotNet bu ölçümleri temiz bir şekilde yapmamızı sağlar.

Şimdi bir örnek üzerinden detaylı inceleyelim. BenchmarkDotNet’i projemize dahil ediyoruz.

PS> Install-Package BenchmarkDotNet

Kodumuzun ne kadar memory kullandığı ile ilgili analiz yapmak için de MemoryDiagnoser paketini dahil ediyoruz.

PS> Install-Package BenchmarkDotNet.Diagnostics.Windows

Bir örnek olarak Linq ile yazılmış methodu Foreach ile yazılmış method ile kıyaslayalım. Methodlarımızı aşağıdaki gibi oluşturuyoruz.

Örneğimizde Enumerable.Range methodu ile oluşturduğumuz int sayı dizisi içerisinde 3'e tam bölünebilen sayıların adetini yazdıralım.

Daha sonra BenchmarkRunner kodumuzu aşağıdaki gibi yazıp Release modda projemizi çalıştırıyoruz.

BenchmarkRunner.Run(typeof(BenchmarkhApp));

Projemizi çalıştırdıktan sonra console ekranında aşağıdaki gibi Finish yazısını gördüğümüz zaman, proje yolunun altında BenchmarkDotNet.Artifacts isimde klasör oluştuğunu ve farklı formatlarda (Markdown, CSV, Html, Xml, Json) dosya içerisine sonuçları yazdığını görüyoruz.

Html formatında üretilen dosyayı açtığımız zaman aşağıdaki gibi sonuçları getirmektedir.

Burada Mean kısmı her bir methodun ortalama çalışma süresini vermektedir. Foreach döngüsü için bu süre 75.62 ms Linq’da 100.07 ms olduğu görülmektedir. Yinelemeler boyunca zamanlama verilerinin hatası ve standart sapması da Error ve StdDev başlıkları altında görülmektedir.

Projemize MemoryDiagnoser ekleyip bellek kullanımı da baktığımız için bellek istatistikleri de gelmiştir. Gen0 kolonu GC koleksiyonu ile ilgilidir. Allocated kolonu ise kodumuz her çalıştığında bellekte 4 KB yer ayırdığını gösteriyor.

BenchmarkDotNet

Konuyu kısaca özetlemek istedim. Şimdi bu kütüphanenin en sevdiğim birkaç özelliğinden bahsedeceğim.

Parameters

Benchmark testlerimizi belirli parametrelere göre çalıştırabilmemizi sağlar.

ParamsSource

Birkaç farklı parametre dizisine göre testlerimizi çalıştırmamızı sağlar.

ParamsAllValues

Bir Enum değerindeki parametrelere göre testlerimizi çalıştırabilmemizi sağlar.

IntroParamsPriority

Çıktı sonuçlarına göre parametre sütunlarını sıralamak için params özniteliği içindeki Priority Önceliği’ni kullanabilirsiniz. Değer aralığı [Int32.MinValue;Int32.MaxValue]’dir, daha düşük öncelikler sütun sırasında daha önce görünür. Varsayılan öncelik 0 olarak ayarlanmıştır.

Arguments

Karşılaştırma ölçütlerine göre argümentler belirlememize yarar.

[Arguments]’ları [Params] ile birleştirebiliriz. Sonuç olarak her [Params] değeri için sonuç alacağız.

Bir örnek yapalım.

Sonuçları aşağıdaki gibidir.

Configs

Config, karşılaştırma ölçütünüzü oluşturmanıza yardımcı olan jobs, columns, exporters, loggers, diagnosers, analysers, validatorskümesidir.

Örnek olarak benchmark testlerimizi farklı formatlarda (Json, Markdown , CSV, Html, Xml) alabilmek için basit bir config sınıfı yazalım. Config classımız ManualConfig’ten türemiştir ve Job, Logger, Column, Exporter, Analyser, Rule gibi olan Benckmark özelliklerini tanımlamamızı sağlar.

ExportXml

BenchmarkDotNet XML formatında çıktı almamızı da sağlar. Aşağıdaki gibi örneğimizi oluşturalım.

fileNameSuffix: hedef dosya adının sonuna yerleştirilecek bir dize.
indentXml=false/true: xml’i biçimlendirmeli miyiz yoksa biçimlendirmemeli miyiz?
replaceMeasurements=false/true: ölçümlerle ilgili ayrıntılı bilgileri hariç tutmalı mıyız?

Çıktısını bu şekilde alıyoruz.

Hesaplanan veriler, grafikler dahil olmak üzere farklı biçimlerde de (md, html, csv, xml, json) dışa aktarılabilir.

ExportJson

fileNameSuffix: hedef dosya adının sonuna yerleştirilecek bir dize.
indentJson=false/true: json’u biçimlendirmeli miyiz?
ExclusiveMeasurements=false/true: ölçümlerle ilgili ayrıntılı bilgileri hariç tutmalı mıyız?

Bir örnek üzerinden gösterelim.

Projemizi çalıştırdığımız zaman json dosyasını görebiliriz.

EtwProfiler

EtwProfiler, Windows’ta .NET kodunun profilini çıkarmaya ve verileri PerfView veya Windows Performance Analyzer ile açılabilen bir izleme dosyasına aktarmaya izin verir.

EtwProfiler, stack trace’leri ve önemli .NET Runtime olaylarını yakalamak için dahili olarak Windows için Event Tracing (ETW) kullanan TraceEvent kitaplığını kullanır.

Benchmark ile process çalıştırılması başlatılmadan önce EtwProfiler, User ve Kernel ETW sessionlarını başlatır. Her session kendi dosyasına veri yazar ve farklı veriler yakalar. User session .NET Runtime olaylarını (GC, JIT vb.) dinlerken, Kernel Session CPU stack ve Hardware Counter olaylarını alır. Daha sonra benhmarked kod ile süreç başlatılır. Benchmark izleme uygulaması sırasında tüm veriler yakalanır ve bir izleme dosyasına yazılır. Ayrıca BenchmarkDotNet Engine, izleme dosyasını analiz ederken kendi özelliklerini kullanır. Benchmark bittiğinde, her iki oturum da kapatılır ve iki izleme dosyası tek bir dosyada birleştirilir.

Referanslar

https://benchmarkdotnet.org

--

--