Minęła dłuższa chwila od ostatniej publikacji bardziej technicznego wpisu ze świata .NET-a. Trzeba to nadrobić! Mój drogi czytelniku, zastanawiałeś się kiedyś jak szybko działa Twój kod? Nie chodzi mi o szybkość wykonywania request – u, czy też poprawnej optymalizacji komunikacji z bazą danych, tylko o działaniu największego mięcha… ten czysty kod, bez żadnych szczegółów architektonicznych, jak baza danych, operacje I/O czy nawet internet. Jeżeli to właśnie ten aspekt programowania zaprząta Twoją głowę, ale jeszcze nie miałeś okazji tego sprawdzić, teraz nadarza się świetna okazja. W tym artykule chciałbym Ci przedstawić jak można wykorzystać bibliotekę BenchmarkDotNet  do mierzenia wydajności kodu.

Quick Start

Do rozpoczęcie przygody z BenchmarkDotNet wystarczy stworzyć pustą aplikację konsolową, a następnie dodać do niej paczkę, o wybitnie zaskakującej nazwie, BenchmarkDotNet 😉 Możemy to zrobić z konsoli, za pomocą polecenia:

$ dotnet add package BenchmarkDotNet

Tym sposobem przygotowujemy całe środowisko. Wszystko poszło gładko, no chyba, że jesteś za korporacyjnym proxy…. wtedy nie będzie to aż tak proste 😛 (jeszcze kilka takich sucharów a podwórko mi wyschnie 😉

Następnie musimy wymyślić, co będziemy testowali. Na potrzeby artykułu załóżmy, że chcemy przetestować realny wpływ PLINQ na czas wykonywania konkretnego zadania. Przyjmijmy, że ze zbioru 10000000 liczb planujemy wyciągnąć tylko te, które zawierają cyfrę “3”. Taki zbiór danych powinien wystarczyć, abyśmy dostrzegli różnicę.

Klasa testowa

Przejdźmy zatem do napisania klasy testowej. Nazwijmy ją LINQ Benchmark

public class LinqBenchmark {
        public IEnumerable<int> numbers;
        
        public LinqBenchmark()
        {
            numbers = Enumerable.Range(1, 10000000).ToArray();
        }
        
        public int[] GetAllWithThree() {
            return numbers.Where(x => x.ToString().Contains("3"))
            .ToArray();
        }
        
        public int[] GetAllWithThreeParallel() {
            return numbers.AsParallel().Where(x => x.ToString()
            .Contains("3")).ToArray();
        }
    }

Nie ma tutaj żadnej czarnej magii, więc wyjaśnienia będą bardzo skrótowe. W konstruktorze tworzymy naszą listę elementów. Następnie mamy dwie metody mające parę oddzielnych zapytań LINQ, z czego druga implementacja wywołuje AsParallel() do wykorzystania mocy współbieżności.

Setup testów

Mając utworzoną klasę testową przechodzimy do wykorzystania biblioteki BenchmarkDotNet. Zacznijmy od pliku startowego naszej aplikacji konsolowej. Konieczne jest, aby dodać w nim dwa elementy: odpowiedni using czyli: BenchmarkDotNet.Running, a także, w głównej metodzie naszego programu, wywołać runner-a

static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<LinqBenchmark>();
}

Pozostało nam dorobić drugiego using-a BenchmarkDotNet.Attributes w klasie LinqBenchmark, a do metod wywołujących nasze implementacje dodać atrybut Benchmark

[Benchmark]
public int[] GetAllWithThree() {
    return numbers.Where(x => x.ToString().Contains("3"))
    .ToArray();
}

[Benchmark]
public int[] GetAllWithThreeParallel() {
    return numbers.AsParallel().Where(x => x.ToString()
    .Contains("3")).ToArray();
}

W ten sposób biblioteka Benchmark będzie wiedzieć jakie metody chcemy przetestować.

Uruchomienie testu

Poprawne działanie BenchmarkDotNet wymaga uruchomienia aplikacji w trybie “Release”. O różnicach pomiędzy trybami Debug i Release pewnie kiedyś napisze parę słów, na ten moment musicie mi uwierzyć na słowo. O ile dobrze pamiętam BenchmarkDotNet ma zabezpieczenie, by nie dało się go uruchomić w trybie Debug. Tak więc odpalmy naszą aplikację w trybie Release

$ dotnet run -c Release

BenchmarkDotNet będzie potrzebował chwili na przetrawienie kodu, uruchomienie go wielokrotnie w izolacji i analizę wyników. Po chwili na konsoli pojawi się bardzo ładnie narysowana tabelka wraz z informacjami oraz dane procesora wykorzystanego do testowania.

 

Jak można było się spodziewać, PLINQ w tym przypadku okazało się szybsze 😉 Proszę jednak, byście nie przerabiali teraz wszystkich zapytań LINQ, ponieważ zmniejszenie liczby elementów do stu daje odwrotny wynik 😉

To by było na tyle jeżeli chodzi o wprowadzenie do testowania wydajności kodu C#. Mam nadzieje że artykuł się podobał 😉

Do Następnego!

Cześć