Midiendo el rendimiento de nuestro código con BenchmarkDotNet
Tags: dotnet, benchmarkdot, csharp, programming
Published: 2023-07-16 15:34:56

Un _Benchmark_ (en Software) es un proceso que nos permite medir el rendimiento de un software, componente, algoritmo o bloque de código y verificar si cumple con los requerimientos establecidos de por ejemplo, uso de memoria o tiempos de ejecución.

Este tipo de pruebas nos permite incluso comparar 2 componentes, 2 versiones de un mismo componente o diferentes algoritmos y verificar cual tiene mejor rendimiento.

# Introducción a BenchmarkDotNet
[BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) es la librería de .Net mas aceptada por la comunidad siendo utilizada por varios proyectos de Microsoft.

_BenchmarkDotNet_ nos permite generar pruebas de _Benchmark_ y ejecutarlas con la misma simplicidad de ejecución que las de testing unitario.

Entre las funcionalidades que podemos encontrar se encuentran:
- Ejecución de multiples Benchmark
- Setup/Cleanup entre ejecuciones
- Parametrización (al igual que por ejemplo XUnit/NUnit)
- Benchamark sobre diferentes plataformas/runtimes: NetFramework, Dotnet, WebAssembly, x86/x64, Mono, AOT
- Diagnosers: de memoria, JIT, Disasembly entre otros
- Exporters: CSV, HTML, RPlot, JSON, Markdown, etc
- Generación de trace files para su posterior análisis en PerfView/Visual Studio Profiler/SpeedScope
- Entre otros

# Creando nuestro primer Benchmark
Nuestro _Benchmark_ debe esta encapsulado en una clase y por cada función atómica de código a analizar le debemos agregar el atributo `[Benchmark]` para que la librería pueda auto-descubrir los bloques de código a ejecutar.

Usaremos [Bogus](https://github.com/bchavez/Bogus) para generar Fake data y el atributo `[GlobalSetup]`para generar una función de inicialización donde inicializamos lo que necesitamos. Ésta función se ejecutará una única vez y no afectará las métricas.

Vamos al ejemplo básico
```csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Bogus;

BenchmarkRunner.Run(typeof(BenchmarkFor));

public class BenchmarkFor
{
    private const int __amountOfItems = 1000;
    private string[] ListOfStrings { get; set; }

    [Benchmark(Baseline = true)]
    public int[] ForClassic()
    {
        var retValues = new int[__amountOfItems];
        for (var i = 0; i < ListOfStrings.Length; i++) 
            retValues[i] = ListOfStrings[i].Length;
        return retValues;
    }

    [Benchmark]
    public int[] ForWithLinQ() => ListOfStrings.Select(x => x.Length).ToArray();

    [GlobalSetup]
    public void Startup()
    {
        var faker = new Faker("en");
        ListOfStrings = Enumerable.Range(0, __amountOfItems)
            .Select(x => faker.Lorem.Word())
            .ToArray();
    }
}
```

Hay que prestar atención que cuando se realizan estas pruebas el entorno donde se ejecutan es muy importante porque puede afectar los tiempos de gran manera. Es recomendable ejecutar el proceso desde la consola (para no attachar el debugger), compilar en modo release y no tener otros procesos corriendo en la notebook y si se puede ejecutar en un entorno aislado (ej en una instancia en cloud), mucho mejor.

Ahora sí, lo ejecutamos...

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbgwv9r8qxyh8h8iwdwk.JPG)

... y luego de esperar un ratito (si, tarda y genera mucho log por pantalla) obtenemos la siguiente información

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcjd2hxoanxedm5gugut.JPG)

Lo más importante es el último recuadro en donde vemos los tiempos promedio que tardo cada función que marcamos como `[Benchmark]`,  siendo la primera, 2x veces más rápida que la opción que utiliza `Linq`. En la misma, el resto de las columnas muestran la desviación estándar, la mediana y el ratio.

Algo importante a considerar en algunos casos es el _Histograma_ que muestra para cada función. En ellos podemos ver como se distribuyeron los tiempos de las sucesivas ejecuciones y ante ciertos casos no ayudará a encontrar puntos de mejora.

Por último, se muestra la sección de _Outliers_ que nos indica cuantos casos fueron eliminados de la muestra porque estuvieron muy por fuera del rango del resto de los casos (ya sea que estuvieron muy por debajo o muy por arriba).

# Conclusión
Conocer como realizar un _Benchmark_ de nuestro código es una habilidad muy útil para tenerla dentro de nuestro _toolbox_ de herramientas como desarrollador y nos va a ser de mucha ayuda cuando queramos medir la performance de nuestro código.