Comparación de rendimiento con gráficos de Benchmark.NET

 C Programming >> Programación C >  >> Tags >> .NET
Comparación de rendimiento con gráficos de Benchmark.NET

El siguiente gráfico compara el tiempo de ejecución de tres implementaciones de clasificación ejecutadas con diferentes tamaños de entrada (1k, 10k, 100k):

Este gráfico se generó usando Benchmark.NET, que mostraré cómo usar en este artículo. Compararé el rendimiento de las implementaciones de clasificación rápida multiproceso (con Array.Sort() sin subprocesos como referencia).

Cree una aplicación de consola y haga referencia a Benchmark.NET

Cree una aplicación de consola específicamente para la evaluación comparativa. Sugiero separar esta aplicación de consola del código que está evaluando para mantener las cosas ordenadas y organizadas (al igual que tendría un proyecto separado para la prueba unitaria de su código bajo prueba).

  • Cree una aplicación de consola.
  • Agregue una referencia al código que está comparando.
  • Instale el paquete nuget de Benchmark.NET con el siguiente comando (Ver> Otras ventanas> Administrador de paquetes ):
Install-Package BenchmarkDotNet
Code language: PowerShell (powershell)

Al final, el archivo .csproj de su aplicación de consola debería verse así:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\ThreadQuickSort\ThreadQuickSort.csproj" />
  </ItemGroup>

</Project>
Code language: HTML, XML (xml)

Nota:En este ejemplo, estoy comparando código en un proyecto llamado ThreadQuickSort.

Crea puntos de referencia y ejecútalos

Con Benchmark.NET, crea una clase de evaluación comparativa. Contiene uno o más métodos marcados con el atributo [Benchmark]. Cuando ejecuta los puntos de referencia, ejecuta estos métodos. Aquí también es donde agrega los datos de prueba de evaluación comparativa.

Crear clase de evaluación comparativa con datos de prueba

Para comparar diferentes algoritmos, es una buena idea compararlos con múltiples tamaños de entrada. Esto también le brinda información sobre el crecimiento real de los métodos (que debe coincidir con el crecimiento teórico determinado por el análisis Big-O).

La siguiente clase de referencia está configurada para generar gráficos para comparar el rendimiento de tres métodos de clasificación (Array Sort, Fork Join Sort y PLINQ Sort) utilizando tres tamaños de entrada:1k, 10k y 100k (según lo especificado por [Params ] atributo):

using BenchmarkDotNet.Attributes;

[RPlotExporter]
public class SortingStringsBenchmarks
{
	[Params(1000, 10_000, 100_000)]
	public int N;

	private string[] copyForForkJoinSort;
	private string[] copyForPLINQSort;
	private string[] copyForBaseline;

	[GlobalSetup]
	public void GlobalSetup()
	{
		var randomArray = SortUtility.GenRandomArray<string>(size: N);
		copyForForkJoinSort = new string[N];
		copyForPLINQSort = new string[N];
		copyForBaseline = new string[N];
		Array.Copy(randomArray, copyForForkJoinSort, N);
		Array.Copy(randomArray, copyForPLINQSort, N);
		Array.Copy(randomArray, copyForBaseline, N);
	}

	[Benchmark]
	public void ForkJoinSort()
	{
	   new ForkJoinSort<string>().Sort(copyForForkJoinSort).GetAwaiter().GetResult();
	}
	[Benchmark]
	public void PLINQSort()
	{
		copyForPLINQSort = copyForPLINQSort.AsParallel().OrderBy(t => t).ToArray();
	}
	[Benchmark(Baseline = true)]
	public void ArraySortBaseline()
	{
		Array.Sort(copyForBaseline);
	}
}
Code language: C# (cs)

El método marcado con [GlobalSetup] El atributo se ejecuta una vez para cada tamaño de entrada. Los métodos de referencia deben usar los mismos datos de prueba y no modificar los datos originales. Esto le permite hacer una comparación de manzanas con manzanas. Es por eso que genera una matriz aleatoria de tamaño N y crea copias de la matriz para cada método de referencia.

Configurar y ejecutar los puntos de referencia

Ahora que tiene la clase de evaluación comparativa, puede ejecutarla usando BenchmarkRunner y pasando una configuración con los exportadores adecuados para generar gráficos.

using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Exporters.Csv;
using BenchmarkDotNet.Running;

static void Main(string[] args)
{
	var config = ManualConfig.CreateMinimumViable();
	config.AddExporter(CsvMeasurementsExporter.Default);
	config.AddExporter(RPlotExporter.Default);

	BenchmarkRunner.Run<SortingStringsBenchmarks>(config);
}
Code language: C# (cs)

Ejecute los puntos de referencia ejecutando la aplicación de la consola. Esto comenzará a ejecutar los puntos de referencia y a iniciar sesión en la consola. Es muy detallado y puede llevar un tiempo generar los resultados.

Ver los resultados

La ejecución de estos puntos de referencia genera lo siguiente en la consola:

|            Method |      N |         Mean |       Error |      StdDev |       Median | Ratio | RatioSD |
|------------------ |------- |-------------:|------------:|------------:|-------------:|------:|--------:|
|      ForkJoinSort |   1000 |     504.9 us |    10.08 us |    12.75 us |     503.5 us |  0.91 |    0.05 |
|         PLINQSort |   1000 |     451.6 us |     8.88 us |    13.30 us |     446.7 us |  0.82 |    0.05 |
| ArraySortBaseline |   1000 |     538.8 us |     9.95 us |    26.91 us |     526.8 us |  1.00 |    0.00 |
|                   |        |              |             |             |              |       |         |
|      ForkJoinSort |  10000 |   5,217.5 us |    29.32 us |    27.42 us |   5,209.4 us |  0.61 |    0.01 |
|         PLINQSort |  10000 |   3,933.5 us |    20.84 us |    17.40 us |   3,931.5 us |  0.46 |    0.01 |
| ArraySortBaseline |  10000 |   8,519.1 us |   105.15 us |   140.38 us |   8,525.9 us |  1.00 |    0.00 |
|                   |        |              |             |             |              |       |         |
|      ForkJoinSort | 100000 |  55,977.7 us | 1,113.65 us | 2,773.38 us |  56,395.9 us |  0.49 |    0.03 |
|         PLINQSort | 100000 |  48,577.0 us |   938.71 us | 1,619.22 us |  48,108.2 us |  0.43 |    0.02 |
| ArraySortBaseline | 100000 | 114,953.8 us | 1,734.35 us | 1,537.45 us | 115,175.2 us |  1.00 |    0.00 |Code language: plaintext (plaintext)

Nota:el tiempo está en microsegundos, que aparece como "nosotros" en la consola.

Los resultados de las pruebas comparativas también se envían al siguiente directorio:\bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\

Ejecutó los puntos de referencia con tamaños de entrada:1k, 10k y 100k (que se especificaron con Params atributo en la clase de referencia). Muestra varias estadísticas agrupadas por nombre de método y tamaño de entrada. Los resultados pueden ser difíciles de interpretar en este formato tabular basado en texto. Aquí es donde entran los gráficos, como mostraré a continuación.

Generar gráficos para comparar

Benchmark.NET genera gráficos utilizando el lenguaje de programación R para trazar los resultados del archivo *-measurements.csv. Es por eso que debe usar los exportadores CsvMeasurementsExporter y RPlotExporter en la configuración.

Instalar R

Primero, necesitas instalar R.

  • Obtenga la última versión de R para su sistema operativo e instálela. (Instalé la versión R-4.1.1-win.exe para Windows)
  • Agregue el directorio \bin\ de R a la variable de entorno del sistema PATH. (El directorio bin para mí era C:\Program Files\R\R-4.1.1\bin\ )
  • Reinicie Visual Studio si estaba abierto para que obtenga la variable PATH actualizada.

Si la variable PATH no se actualiza correctamente, verá el siguiente error cuando ejecute los puntos de referencia:

Benchmark.NET en realidad crea un archivo de script R llamado BuildPlots.R en el directorio de salida de la compilación. Siempre que tenga el archivo *-measurements.csv, puede ejecutar este script manualmente desde la línea de comando si lo desea. Esto sería útil si no desea generar siempre los gráficos cada vez que ejecuta los puntos de referencia:

RScript.exe \bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\BuildPlots.R
Code language: R (r)

Ejecute los puntos de referencia y mire los gráficos

Ahora que R está instalado, vuelva a ejecutar los puntos de referencia (ejecutando la aplicación de la consola).

Los gráficos resultantes se muestran aquí:\bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\.

Hay una gran cantidad de imágenes gráficas. Los gráficos de comparación se denominan *-barplot y *-boxplot. Eche un vistazo al gráfico *-barplot:

Esto le permite comparar visualmente los diferentes métodos de clasificación para cada tamaño de entrada. El método PLINQ Sort fue el más rápido y fue más de 2 veces más rápido que el método Array Sort.

Incluir el uso de memoria en la comparación de rendimiento

Es común observar principalmente el tiempo de ejecución al comparar el rendimiento, pero si desea obtener una imagen completa, no se olvide de comparar también el uso de la memoria.

Para incluir estadísticas de uso de memoria, agregue el [MemoryDiagnoser] atributo a la clase de evaluación comparativa:

[RPlotExporter]
[MemoryDiagnoser]
public class SortingStringsBenchmarks
{
	//rest of class
}
Code language: C# (cs)

Nota:también puede agregarlo a la configuración con AddDiagnoser(MemoryDiagnoser.Default).

La ejecución de los puntos de referencia genera los siguientes resultados:

|            Method |      N |         Mean |    Allocated |
|------------------ |------- |-------------:|-------------:|
|      ForkJoinSort |   1000 |     542.9 us |      9,553 B |
|         PLINQSort |   1000 |     435.8 us |    161,024 B |
| ArraySortBaseline |   1000 |     514.0 us |          8 B |
|                   |        |              |              |
|      ForkJoinSort |  10000 |   5,244.5 us |     81,557 B |
|         PLINQSort |  10000 |   3,966.0 us |  1,413,354 B |
| ArraySortBaseline |  10000 |   8,318.5 us |            - |
|                   |        |              |              |
|      ForkJoinSort | 100000 |  58,397.6 us |    801,552 B |
|         PLINQSort | 100000 |  48,294.1 us | 13,049,361 B |
| ArraySortBaseline | 100000 | 116,495.0 us |            - |Code language: plaintext (plaintext)

Nota:se eliminaron varias columnas por razones de brevedad.

El método PLINQSort es el más rápido, pero también usa la mayor cantidad de memoria por un margen significativo (17 veces más que ForkJoinSort).

Esto muestra por qué es importante no ignorar la memoria al comparar el rendimiento. Se trata de encontrar el equilibrio adecuado entre la eficiencia del tiempo y el espacio en función de las limitaciones de recursos a las que se enfrentará su software en producción. A veces querrá el método más rápido (PLINQSort), a veces querrá el método más eficiente en el espacio (ArraySortBaseline), pero la mayoría de las veces querrá ir con el enfoque equilibrado que es lo suficientemente rápido y relativamente eficiente en el espacio ( ForkJoinSort).