
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:

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

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:

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

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.
**La entrada Como medir el rendimiento de nuestro código se publicó primero en Fixed Buffer.**