İlkay İlknur

just a developer...

BenchmarkDotNet ile Performans ve Memory Benchmarking

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bir ürün geliştirirken ne kadar dikkat ediyoruz bilmiyorum ama geliştirdiğimiz uygulamanın en önemli özelliklerinden biri de hiç kuşkusuz ki uygulamanın performansı. Kimi zaman "abi önce çalışsın, sonra performansa bakarız" dedikten sonra ne yazık ki o "sonra" hiçbir zaman gelmiyor taa ki müşteriden bir şikayet alana yada out of memory exception gelene kadar :D

Bu yazıda sizlere benim de arada kullandığım bir benchmarking aracı olan DotNetBenchmark'tan bahsedeceğim. Yukarıda bahsettiğim gibi müşteriden bir şikayet veya exceptionlar almadığımız sürece bu performans veya memory kullanımı olaylarına pek bakmayız. Ama bunlar geldiği zaman da kodların aralarına Stopwatch'lar koyarak süreleri veya memory'i ölçmeye çalışırız. Bu sırada yazdığımız kod da kirlenir vs.. sonra bir de bu ölçüm yaptığımız kodları toparlamak gerekir, zorlanırız. Ayrıca da ölçüm için doğru yöntemleri kullandığımızdan emin olmamız gerekiyor. BenchmarkDotNet aracı işte tüm bu pis işleri de kendisi doğru bir şekilde halletiği için de oldukça güzel bir tool. Şimdi gelelim bu toolu nasıl kullanacağımıza.

İlk olarak BenchmarkDotNet librarysini aşağıdaki komutla Nuget paketi olarak projenize ekleyebilirsiniz.

PS> Install-Package BenchmarkDotNet 

Projenize paketi ekledikten sonra artık herşey oldukça basit. Şimdi diyelim ki bir liste üzerinde LINQ sorgusu ile normal for döngüsünü karşılaştırmak istiyorsunuz. Aşağıdaki gibi basit bir class içerisinde aynı sorguyu hem LINQ ile hem de for döngüsü ile yazıyoruz. Daha sonra yapmamız gereken şey ise metotları Benchmark attribute'ü ile işaretlemek.

public class LINQvsFor
{
    private List<string> list = Enumerable.Range(1, 2000).Select(t => t + "name").ToList();

    [Benchmark]
    public List<string> For()
    {
        List<string> retVal = new List<string>();
        for (int i = 0; i < list.Count; i++)
        {
            if (list[i].Contains("1"))
            {
                retVal.Add(list[i]);
            }
        }
        return retVal;
    }
    [Benchmark]
    public List<string> LINQ()
    {
        return list.Where(t => t.Contains("1")).ToList();
    }
}

Daha sonra BenchmarkRunner sınıfını kullanarak bir console app üzerinde bu testi çalıştırıp sonucunu hızlı bir şekilde görebiliriz. Tabi sonuçları en doğru şekilde alabilmek için testi Release modda çalıştırmayı unutmamak gerekiyor :)

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<LINQvsFor>();
    }
}

Benchmark testlerini çalıştırdıktan sonra çıktıları pek çok farklı formatta(Markdown,CSV) vs.. alabilmemiz mümkün. Bunun için basit bir config sınıfı yazıp benchmarkı çalıştırırken configi parametre olarak geçmemiz gerekiyor. Örneğin Markdown olarak export almak istersek...

class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<LINQvsFor>();
    }
}

public class Config : ManualConfig
{
    public Config()
    {
        Add(MarkdownExporter.Default);
    }
}

Benchmark testlerimizi belirli parametre kombinasyonlarında da çalıştırmamız mümkün. Bunun için ilgili fieldı Params attribute'ü ile işaretleyip alacağı değerleri belirtmemiz yeterli.

Örneğin,

public class StringConcatTest
{
    [Params(1, 2, 3, 4, 5, 10, 100, 1000)]
    public int loops;

    [Benchmark]
    public string StringConcat()
    {
        string result = string.Empty;
        for (int i = 0; i < loops; ++i)
            result = string.Concat(result, i.ToString());
        return result;
    }

    [Benchmark]
    public string StringBuilder()
    {
        StringBuilder sb = new StringBuilder(string.Empty);
        for (int i = 0; i < loops; ++i)
            sb.Append(i.ToString());
        return sb.ToString();
    }
}

Yukarıdaki testler Params attribute'ünde belirttiğimiz parametreler için teker çalıştırılacak. Sonuçlarını da belirtilen her parametre için ayrı ayrı alacağız.

Kodunuzun ne kadar memory kullandığı ile ilgili analiz yapmak isterseniz de öncelikli olarak bir ek nuget paketini daha projenize eklemeniz gerekiyor.

PS> Install-Package BenchmarkDotNet.Diagnostics.Windows 

Bu paketi de ekledikten sonra aşağıdaki gibi MemoryDiagnoser ekleyerek memory kullanımı ile ilgili sonuçları da elde edebiliriz.

class Program
{
    static void Main(string[] args)
    {
        var config = new ManualConfig();
        config.Add(DefaultConfig.Instance);
        config.Add(new MemoryDiagnoser());

        BenchmarkRunner.Run<StringConcatTest>(config);
    }
}

Uygulamayı çalıştırdığımızda...

Gördüğünüz gibi BenchmarkDotNet toolu oldukça kuvvetli bir tool. Bu yazıda bahsedemeyeceğim pek çok farklı özelliği de ayrıca içerisinde barındırmakta. Örneğin testlerini belirli ortamlar için çalıştırabiliyorsunuz. Bir testi base olarak alıp diğer testlerin sonuçlarını ona göre karşılatırabilmeniz de mümkün. Projenin temel amacı benchmarkingi kolay ve güvenilir olarak yapmak olduğu için aslında oldukça complex testleri hızlı bir şekilde 1-2 ufak değişiklikle yapabiliyorsunuz. Bu nedenle testlere başlamadan önce projenin readme sayfasını okumanızı tavsiye ederim.

Projenin Github reposunda ek olarak pekçok örnek benchmark testleri de bulunmakta. Yukarıda kullandığım string concat testi de dahil olmak üzere bu testler için de https://github.com/PerfDotNet/BenchmarkDotNet/tree/stable/BenchmarkDotNet.Samples klasörüne bakabilirsiniz.

Benchmark olayı kritik bir konu olduğu için projenin kullanıldığı yerlerin referanslarına da https://github.com/PerfDotNet/BenchmarkDotNet/wiki/People-using-BenchmarkDotNet adresinden ulaşabilirsiniz. Proje ile ilgili daha detaylı bilgiye de projenin contributerlarından olan Matt Warren'ın blogundan erişebilirsiniz.



Yorum Gönder