Como medir el rendimiento de nuestro código

Tiempo de lectura: 5 minutos
La imagen muestra un medidor de velocidad para el entrada "Como medir el rendimiento de nuestro código"

A raíz de las entradas sobre la serie de entradas sobre la reflexión que publicamos antes del parón de verano, he estado escribiéndome con un lector sobre algunas posibles optimizaciones en el código, todas ellas midiendo el rendimiento del código para ver cuál era mejor. Llevaba tiempo queriendo escribir sobre este tema, ¿y que mejor momento qué ahora? 🙂

¿Cómo se mide el rendimiento del código?

Para conseguir datos que nos sirvan para esto, se utilizan pruebas de rendimiento o «benchmarks», los cuales nos van a servir para obtener las métricas que vamos a comparar (tiempo, cpu consumida, ram utilizada…) para decidir si nuestro código tiene el rendimiento esperado. Normalmente, esto se consigue midiendo los datos a comprobar y ejecutando el código un número suficiente de veces para asegurar que los posibles «ruidos externos» no afectan a las métricas.

Imagina que simplemente medimos el tiempo de ejecución de método ejecutándolo solo una vez, cualquier cosa que el sistema operativo ejecute podría falsear los resultados…

En el ecosistema .Net, existe una librería que se distribuye a través de NuGet que nos facilita enormemente esta labor, y es de la que os voy a hablar ahora, esta librería es BenchmarkDotNet, Vamos a ponernos en faena:

Como usar BenchmarkDotNet

Vamos a imaginar que dentro de nuestro código tenemos algo como esto:

public class OperacionesMatematicas
{
    public double Suma(double a, double b)
    {
        return a + b;
    }

    public double Multiplicacion(double a, double b)
    {
        return a * b;
    }

    public double Potencia(double @base, double exponente)
    {
        return Math.Pow(@base, exponente);
    }

    public double Potencia2(double @base, double exponente)
    {
        if (exponente == 0)
            return 1;

        var resultado = @base;
        for (int i = 1; i < exponente; i++)
        {
            resultado = resultado * @base;
        }
        return resultado;
    }
}

Simplemente es una clase que va a hacer ciertas operaciones matemáticas, teniendo además dos maneras de calcular una potencia, utilizando Math.Pow y multiplicando la base por si misma tantas veces como indica el exponente (lo que es una potencia vamos…).

Lo primero que vamos a necesitar para medir el rendimiento de nuestro código, es crear un proyecto de consola que será el que tenga el código para las pruebas, en este caso, lo voy a llamar «BenchmarkRunnerProject». A este proyecto le vamos a añadir el paquete «BenchmarkDotNet«, y vamos a crear una clase donde vamos a añadir la lógica de la prueba:

public class OperacionesMatematicasBenchmark
{
    [Benchmark]
    public void Suma()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Suma(10, 20);
    }

    [Benchmark]
    public void Multiplicacion()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Multiplicacion(10, 20);
    }

    [Benchmark]
    public void Potencia()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Potencia(2, 2);
    }

    [Benchmark]
    public void Potencia2()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Potencia2(2, 2);
    }
}

Por último, solo nos queda añadir al método Main la ejecución de las pruebas:

class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<OperacionesMatematicasBenchmark>();
        Console.Read();
    }
}

Una vez hecho esto, ya solo necesitamos ejecutar el proyecto para que nuestra prueba funcione. Pero ojo, tiene que estar en «Release» para poder medir el rendimiento del código con las optimizaciones del compilador. En caso contrario, nos mostrará un mensaje indicándonos que lo hagamos o desactivemos que tenga que ser Release:

La imagen muestra la salida de la consola cuando ejecutamos un benchamrk sin estar en release

Una vez que se ejecute, nos mostrará los resultados en la propia consola:

La imagen muestra un ejemplo de la salida básica

Con esto, ya tenemos la funcionalidad básica, donde solo medimos el tiempo de ejecución con los parámetros que hemos puesto «hardcoded». Pero tenemos la opción de indicar diferentes parámetros con el atributo «Params»:

public class OperacionesMatematicasBenchmark
{
    [Params(2, 3)]
    public int A { get; set; }

    [Params(2, 200)]
    public int B { get; set; }

    [Benchmark]
    public void Suma()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Suma(A, B);
    }

    [Benchmark]
    public void Multiplicacion()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Multiplicacion(A, B);
    }

    [Benchmark]
    public void Potencia()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Potencia(A, B);
    }

    [Benchmark]
    public void Potencia2()
    {
        var operaciones = new OperacionesMatematicas();
        operaciones.Potencia2(A, B);
    }
}

Con este cambio, vamos a conseguir que se ejecute una prueba con cada una de las configuraciones:

La imagen muestra los resultados de la ejecución son parametros

E incluso ordenarlas si añadimos el atributo «RankColumn» a la clase:

La imagen muestra el resultado con el atributo rankcolum

Existen muchísimas opciones y parametrizaciones que pueden servirte para configurar cada uno de los aspectos del benchmark, e incluso comparando la ejecución en diferentes entornos (.Net Framework, .Net Core, Mono) y diferentes versiones de los entornos. Te recomiendo que le eches un ojo a la documentación para ver todas las opciones disponibles, ya que, es imposible hablar de todas ellas en una única entrada. (Por ejemplo, para medir la RAM con Diagnosers).

Como siempre, dejo el enlace al repositorio en GitHub con el código de la entrada. Además, si eres una persona interesada en el rendimiento del código, tal ve te interese como escribir código de alto rendimiento con .Net Core.

4 comentarios en «Como medir el rendimiento de nuestro código»

    • Hola Yamyamexe.
      Muchas gracias por tu comentario!!!
      La verdad es que a mi me resulta muy útil de cuando en cuando ejecutar el benchmark para saber si los cambios que voy haciendo mejoran o empeoran.
      Me alegro que te haya sido de utilidad! 🙂 🙂

      Responder
  1. ¡Muy buena entrada Jorge! (como todas las que nos tiene acostumbrado Jorge)

    Lo único resaltar un pequeño fallo que además creo que canta en los resultados, y es que en la multiplicación sumas en vez de multiplicar.

    Puedes notar que es incorrecto en los resultados, ya que aunque a día de hoy las CPU puedan hacer una multiplicación a la misma velocidad que una suma (históricamente no siempre fue así, siendo la multiplicación más costosa) y haciendo que actualmente el orden de las respectivas operaciones sea el mismo, es muy sospechoso que tienda a 0 para los resultados de la multiplicación. Debiera arrojar unos tiempos similares a la suma.

    Sin ser experto en C#, y por lo que Jorge alguna vez me ha contado, pudiera ser por el compilador JIT (Just-In-Time) que realiza mejoras de rendimientos iterativas. Si no fuera por el nombre del método, ambos métodos son idénticos, con lo que no sé si el compilador se dará cuenta y lo tratará como un alias de la operación suma previa que ha ejecutado varias veces en el benchmark.

    Sea esta la razón o no, creo que es evidente que la multiplicación debería multiplicar. Te subo un PR al proyecto ?

    Responder
    • Muchas gracias por el comentario!!!
      He revisado el código en la entrada y ya he arreglado el fallo. Respecto al tiempo… Sí es raro que marque un 0, no es relevante el valor para la finalidad de la entrada, pero es curioso que marque un 0. Le echaré un ojo cuando tenga un rato y te digo a ver que es, puede que sea algún fallo en la librería.

      Atte

      Responder

Deja un comentario