Jorge Serrano
  • Home

Utilizando la librería BenchmarkDotNet

  • By jorge
  • Ene-8-2020
  • .NET Core, .NET Core 3.0, .NET Core 3.1, .NET Framework 4.7, .NET Framework 4.8, Performance, Rendimiento
  • 0 Comments.

Introducción

Muchos programadores tenemos el foco principal (casi único) puesto en la importancia que tiene cubrir funcionalmente la lógica que se demanda de una porción o rutina de código.
Algunos programadores tenemos en consideración además, que el código sea legible, mantenible, con cierto criterio de organización lógica, y aplicando buenas prácticas.
Otros programadores, los menos, tenemos en cuenta adicionalmente, aspectos relativos al rendimiento, fiabilidad y seguridad.

Existen multitud de herramientas y libros que abordan cada una de estas cosas que comento, pero combinar todas de forma eficiente y correcta no es tarea fácil, incluso para programadores experimentados.
El hecho es que muchas empresas contratan a programadores en sus equipos que saben cubrir alguna de estas facetas, pero en las contrataciones casi nunca se muestra especial atención al ciclo completo, y es que la verdad sea dicha, es complicado hacerlo.

En esta entrada, voy a tratar de hablar de tareas relacionadas con el rendimiento, de suma importancia y que muchas veces pasamos por alto o lo dejamos para «ya lo veremos más adelante», no abordándolo casi nunca al final.

Dentro del mundo .NET existen muchas herramientas que nos ayudarán en este propósito.
Mi deseo es poder hablar aquí de una de ellas, Benchmark DotNet.

 

¿Qué es BenchmarkDotNet?

BenchamarkDotNet es una herramienta a través de la cual podremos analizar el rendimiento de nuestra aplicación y anticiparnos incluso a problemas que podrían arrastrarse a producción.
La idea detrás de esta herramienta es la de ser capaces de medir el rendimiento de nuestro código .NET tratando de que sea lo más eficiente posible.

Este paquete generará un proyecto aislado por cada método que marquemos con el decorador Benchmark, y se encargará de realizar por nosotros, lanzamientos del proyecto ejecutándolo en varias iteraciones.
Eso permitirá ejecutar pruebas de rendimiento, extraer resultados, y analizar los mismos.
Todo en pocas líneas de código.

Los informes que la herramienta extrae los obtendremos en diferentes formatos (CSV, HTML, etc).
Los datos que recogeremos por defecto, son los que la herramienta considera como más importantes.

Para más detalle, lo mejor es consultar la documentación de BenchmarkDotNet.

El código fuente de esta herramienta está abierto y puede ser consultado en este enlace.

 

¿Cómo usarlo?

Una vez explicado brevemente en qué consiste Benchmark, voy a tratar de explicar su uso con un ejemplo.

Crearemos un proyecto de consola.

Abriremos las opciones del proyecto y nos posicionaremos dentro de la solapa Build.
En esta sección, habilitaremos la opción Optimize code.

Una vez hecho esto, incluiremos el paquete NuGet de BenchmarkDotNet que encontraremos en este enlace dentro del proyecto.
El paquete es compatible con .NET Framework, Mono o .NET Core.

Crearemos una clase dentro de la cual agregaremos los métodos cuyo rendimiento queremos medir.
Agregaremos los métodos que queremos medie y marcaremos cada uno de ellos con el atributo Benchmark.

Por ejemplo, en mi caso voy a medir el rendimiento en operaciones de concatenación de cadenas de texto con Text y con StringBuilder:

[RankColumn]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[MemoryDiagnoser]
public class DemoBenchmark
{
  private int iterations = 10000;

  [Benchmark]
  public string TextDemo()
  {
    var text = String.Empty;

    for (int i = 0; i < iterations; i++)
      text += $"text{i}";

    return text;
  }

  [Benchmark]
  public string StringBuilderDemo()
  {
    var stringBuilder = new StringBuilder();

    for (int i = 0; i < iterations; i++)
      stringBuilder.Append($"text{i}");

    return stringBuilder.ToString();
  }
}

Como podemos apreciar en este código, la clase la he decorado también con diferentes atributos.

RankColumn nos indica el rango o posición en la que se encuentra el resultado obtenido.

Orderer permite personalizar el orden del resultado de pruebas de rendimiento en la tabla resumen.
En nuestro caso, ordenamos los resultados de más rápido a más lento.
Podemos combinar varias ordenaciones separadas por comas.

MemoryDiagnoser nos permite medir el número de bytes asignados y la frecuencia del garbage collection.
Para más información, te sugiero leer esta entrada de Adam Sitnik

Finalmente, en la clase Program agregaré el siguiente código que es el que permitirá ejecutar los procesos que permitan medir el rendimiento de nuestro código.

public class Program
{
  public static void Main(string[] args)
  {
    var summary = BenchmarkRunner.Run<DemoBenchmark>();

    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine("Benchmark finished!. Press any key to close");
    Console.ResetColor();
    Console.ReadKey();
  }
}

 

Obteniendo los resultados

Podremos iniciar el proceso desde Visual Studio o bien desde la carpeta bin en la que hayamos compilado nuestro código.
Por otro lado, mi recomendación es hacerlo así y desde Release, aunque no es obligatorio, pero los resultados de las pruebas serán más fiables haciéndolo de esta forma.

Los resultados de ejecutar este código que he obtenido en mi máquina son los siguientes:

 

Combinando Frameworks en los resultados

Mi ejemplo ha sido realizado en .NET Core 3.1, sin embargo, podríamos querer combinar los resultados con diferentes Frameworks al mismo tiempo.

Imaginemos que queremos analizar los resultados en .NET Core 3.1 y .NET Framework 4.7.2 al mismo tiempo.

Bastará con agregar el Framework en el proyecto (.csproj) como por ejemplo de esta forma (puedes indicar la versión o versiones de frameworks que quieres analizar o indicar en tu proyecto en este enlace).

Nota: TargetFramework cambia aquí por TargetFrameworks.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>netcoreapp3.1;net472</TargetFrameworks>
  </PropertyGroup>

  ...

</Project>

Recuerda que puedes descargar e instalar las diferentes versiones de .NET Framework y .NET Core desde este enlace.

Por último, en la clase DemoBenchmark que indiqué anteriormente, voy a agregar una nueva decoración:

...
[SimpleJob(RuntimeMoniker.Net472), SimpleJob(RuntimeMoniker.CoreRt31)]
public class DemoBenchmark
{
  ...
}

De esta forma, cuando ejecutemos nuevamente nuestra aplicación de consola, ésta lanzará las pruebas en diferentes versiones del Framework.

Los resultados que he obtenido en este caso son los siguientes:

Estudiando los resultados

Finalmente quedaría interpretar y estudiar los resultados.

Mean, indica la media medida en este caso en microsegundos.
Como vemos, se trata de una media de tiempo muy baja.
Algo más de 1000x mejor para StringBuilder que para Text.

Gen 0 nos indica el número de recolecciones de objetos de primera generación en elementos de pequeño tamaño.
Aquí se almacenan objetos de corta existencia o variables temporales.
El garbage collection suele actuar de forma frecuente en este nivel.

Gen 1 contiene objetos de corta existencia y sirve como buffer entre los objetos de corta existencia y los objetos de larga duración.

Gen 2 contiene objetos de larga existencia como los datos estáticos que tienen una duración a lo largo de los diferentes procesos que puedan incurrir en la ejecución de una aplicación.

Allocated nos indica la memoria ocupada por el head en cada operación.
Cuanto menor sea este valor, menos carga para el recolector de basura, más optimización de memoria para el proceso, y mejor rendimiento generalmente.

 

Ejecutando las pruebas de rendimiento con diferentes datos de partida

Imaginemos que sobre el ejemplo expuesto, quiero comparar los resultados en iteraciones de 100, 1000 y 10000 elementos.
Podríamos cambiar la variable que indica las interaciones y ejecutar tres veces las pruebas, o bien, podría hacer uso del atributo Params que nos permitirá ejecutar pruebas de rendimiento con valores de partida distintos.

En nuestro caso, modificaremos la clase de pruebas de rendimiento para que acepte estos datos de entrada de esta forma:

...
[Params(100, 1000, 10000)]
public int Iterations { get; set; }
...

Si ejecutáramos nuevamente el proyecto para .NET Core 3.1, obtendríamos unos resultados parecidos a los siguientes:

Conclusiones

Como podemos apreciar, trabajar con BenchmarkDotNet es realmente sencillo y rápido, y nos permite obtener una serie de resultados muy valiosos.

Un aspecto adicional que no me gustaría dejar pasar por alto, es que no nos debemos volver unos obsesivos compulsivos con la aplicación y uso de las pruebas de rendimiento.
Aunque todo es susceptible de ser mejorado, lo cierto es que aplicar esfuerzo y tiempo en mejorar una pequeñísima parte de nuestro código, o tratar de mejorar algo que ya de por sí está cerca de tener un muy buen rendimiento, no tiene a priori sentido.

Debemos ser nosotros los que marquemos la prioridad de qué parte o qué partes de nuestro código no son susceptibles de mejorar y cuales sí de acuerdo al rendimiento, y hacer un estudio previo de las posibles combinaciones de código que podrían mejorar nuestra aplicación y porqué.

Finalmente, indicar que toda la documentación, información y otros detalles sobre BenchmarkDotNet, la podrás consultar en este enlace.

Happy Coding!

Comments

Deja un comentario Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

← Previous Post Next Post →

Jorge Serrano

MVP Reconnect


¡Subscríbete a mi canal!
YouTube

Donaciones
Donation

Entradas recientes

  • Go – Arrays
  • Go – Operators
  • Go – Constants
  • Go – Tipos de Datos
  • Go – Variables
  • Hello Go-rld!
  • Introducción a Go o Golang
  • JSON Patch en ASP.NET Core 5 Web API
  • Null Checking en C#
  • ¿Porqué mi página web por defecto de ASP.NET Core no se vé en mi Azure Web App y me da un 404?

Categorías

  • .NET 5
  • .NET Core
  • .NET Core 3.0
  • .NET Core 3.1
  • .NET Framework 2.0
  • .NET Framework 3.0
  • .NET Framework 3.5
  • .NET Framework 4.0
  • .NET Framework 4.5
  • .NET Framework 4.6
  • .NET Framework 4.7
  • .NET Framework 4.8
  • .NET Standard 2.0
  • .NET Standard 2.1
  • AMQP
  • Android
  • Angular
  • API REST
  • Apple
  • Apple iOS
  • Apple macOs
  • Arquitectura
  • ASP.NET
  • ASP.NET Core
  • ASP.NET Core 3
  • ASP.NET Core 5
  • AWS
  • Azure App Service
  • Azure Application Insights
  • Azure Cosmos DB
  • Azure Database Migration Service
  • Azure Databricks
  • Azure DevOps
  • Azure Event Grid
  • Azure Functions
  • Azure IoT
  • Azure Portal
  • Azure PowerShell
  • Azure Queue Storage
  • Azure SQL
  • Azure Storage
  • Azure Virtual Datacenter
  • Azure WebApps
  • Big Data
  • Bing
  • Blazor
  • Blog
  • Bots
  • C#
  • C# 7.0
  • C# 7.1
  • C# 7.2
  • C# 7.3
  • C# 8.0
  • C# 9.0
  • Channel 9
  • Codeplex
  • Codespaces
  • Containers
  • Debugging
  • DevOps
  • Docker
  • Electron
  • Entity Framework
  • Entity Framework Core
  • Entity Framework Core 3.0
  • Entity Framework Core 5
  • Eventos
  • F#
  • FaaS
  • FeatureFlags
  • FeatureToggles
  • Feeds
  • Fluent Assertions
  • General
  • GIMP
  • Git
  • GitHub
  • Go
  • Google
  • Google Analytics
  • Gradle
  • gRPC
  • GSA
  • Historia de la Informática
  • HoloLens
  • HtmlAgilityPack
  • IdentityServer4
  • Inkscape
  • Ionic
  • iOS
  • IoT
  • Java
  • JavaScript
  • JDBC
  • JSON
  • Kubernetes
  • Lenguajes de Programación
  • Libros y Cursos
  • LINQ
  • Linux
  • LiteDB
  • Machine Learning
  • macOS
  • Microservices
  • Microsoft
  • Microsoft .NET Framework 4.5
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Build
  • Microsoft Ignite
  • Microsoft Learn
  • Microsoft Orleans
  • Microsoft Surface Go
  • Microsoft Teams
  • ML.NET
  • MQTT
  • MRO
  • MS-DOS
  • MsCoders Madrid
  • MVP
  • NancyFx
  • Node.js
  • NoSQL
  • NuGet
  • NUnit
  • OData
  • ODP.NET Core
  • Office 2007
  • Office 2010
  • Office 2013
  • Office 2016
  • Office 2019
  • Office 365
  • Open Source
  • Open XML SDK
  • Opinión
  • Orchard CMS
  • OT
  • PaaS
  • Patterns
  • PdfSharpCore
  • Performance
  • PHP
  • Postman
  • Power BI
  • PowerShell
  • PowerShell Core
  • Productividad
  • Project Server 2019
  • R
  • Rendimiento
  • Scala
  • Scraper
  • Security
  • Serverless
  • Service Fabric
  • SharePoint Server 2019
  • SignalR
  • Sin categoría
  • Sistemas Distribuidos
  • Skype
  • Skype for Business Server 2019
  • Small Basic Online
  • SQL Server 2005
  • SQL Server 2008
  • SQL Server 2012
  • SQL Server 2014
  • SQL Server 2016
  • SQL Server 2017
  • SQL Server 2019
  • STOMP
  • Swagger
  • Testing
  • TFS 2017
  • TFS 2018
  • Tools
  • TypeScript
  • Unity
  • UWP
  • UX
  • Visio
  • Visual Basic
  • Visual Studio 2010
  • Visual Studio 2012
  • Visual Studio 2013
  • Visual Studio 2015
  • Visual Studio 2017
  • Visual Studio 2017 for Mac
  • Visual Studio 2019
  • Visual Studio 2019 for Mac
  • Visual Studio App Center
  • Visual Studio Code
  • Visual Studio IntelliCode
  • Visual Studio Live Share
  • Visual Studio Live Share Audio
  • Visual Studio Online
  • VS Anywhere
  • Vue.js
  • Web API
  • WebAssembly
  • WinDbg
  • Windows
  • Windows 10
  • Windows Compatibility Pack
  • Windows Phone 10
  • Windows Phone 7
  • Windows Phone 8
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2016
  • Windows Server 2019
  • Windows Service
  • WinForms
  • WinUI
  • WPF
  • Xamarin
  • Xbox
  • Xcode
  • Xiaomi Mi Band 2
  • xUnit
  • YAML

Archivos

  • enero 2021
  • diciembre 2020
  • noviembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • julio 2020
  • junio 2020
  • mayo 2020
  • abril 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • diciembre 2019
  • noviembre 2019
  • octubre 2019
  • septiembre 2019
  • agosto 2019
  • julio 2019
  • junio 2019
  • mayo 2019
  • abril 2019
  • marzo 2019
  • febrero 2019
  • enero 2019
  • diciembre 2018
  • noviembre 2018
  • octubre 2018
  • septiembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • abril 2018
  • marzo 2018
  • febrero 2018
  • enero 2018
  • diciembre 2017
  • noviembre 2017
  • octubre 2017
  • septiembre 2017
  • agosto 2017
  • julio 2017
  • junio 2017
  • febrero 2015
  • octubre 2014
  • junio 2014
  • marzo 2014
  • febrero 2014
  • enero 2014
  • diciembre 2013
  • septiembre 2013
  • agosto 2013
  • julio 2013
  • junio 2013
  • abril 2013
  • febrero 2013
  • enero 2013
  • diciembre 2012
  • noviembre 2012
  • septiembre 2012
  • agosto 2012
  • junio 2012
  • mayo 2012
  • abril 2012
  • marzo 2012
  • febrero 2012
  • enero 2012
  • diciembre 2011
  • noviembre 2011
  • octubre 2011
  • septiembre 2011
  • agosto 2011
  • julio 2011
  • junio 2011
  • mayo 2011
  • abril 2011
  • marzo 2011
  • enero 2011
  • diciembre 2010
  • noviembre 2010
  • octubre 2010
  • septiembre 2010
  • agosto 2010
  • julio 2010
  • junio 2010
  • mayo 2010
  • abril 2010
  • marzo 2010
  • febrero 2010
  • enero 2010
  • diciembre 2009
  • noviembre 2009
  • octubre 2009
  • septiembre 2009
  • agosto 2009
  • julio 2009
  • junio 2009
  • mayo 2009
  • abril 2009
  • marzo 2009
  • febrero 2009
  • enero 2009
  • diciembre 2008
  • noviembre 2008
  • octubre 2008
  • septiembre 2008
  • agosto 2008
  • julio 2008
  • junio 2008
  • mayo 2008
  • abril 2008
  • marzo 2008
  • febrero 2008
  • enero 2008
  • diciembre 2007
  • noviembre 2007
  • octubre 2007
  • septiembre 2007
  • agosto 2007
  • julio 2007
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007
  • febrero 2007
  • enero 2007
  • diciembre 2006
  • noviembre 2006
  • octubre 2006
  • septiembre 2006
  • agosto 2006
  • julio 2006
  • junio 2006
  • mayo 2006
About This Site

A cras tincidunt, ut tellus et. Gravida scel ipsum sed iaculis, nunc non nam. Placerat sed phase llus, purus purus elit.

Archives Widget
  • January 2010
  • December 2009
  • November 2009
  • October 2009
Categories
  • Entertainment
  • Technology
  • Sports & Recreation
  • Jobs & Lifestyle
Search
  • twitter

Powered by WordPress  |  Business Directory by InkThemes.

This site uses cookies: Find out more.