Leistungsvergleich mit Benchmark.NET-Diagrammen

 C Programming >> C-Programmierung >  >> Tags >> .NET
Leistungsvergleich mit Benchmark.NET-Diagrammen

Das folgende Diagramm vergleicht die Ausführungszeit von drei ausgeführten Sortierimplementierungen mit unterschiedlichen Eingabegrößen (1k, 10k, 100k):

Dieses Diagramm wurde mit Benchmark.NET erstellt, dessen Verwendung ich in diesem Artikel zeigen werde. Ich werde die Leistung von Multithread-Quicksort-Implementierungen vergleichen (mit dem nicht-threaded Array.Sort() als Grundlage).

Konsolenanwendung erstellen und auf Benchmark.NET verweisen

Erstellen Sie eine Konsolen-App speziell für das Benchmarking. Ich schlage vor, diese Konsolen-App von dem Code zu trennen, den Sie bewerten, um die Dinge schön und organisiert zu halten (so als hätten Sie ein separates Projekt zum Testen von Komponenten Ihres zu testenden Codes).

  • Erstellen Sie eine Konsolen-App.
  • Fügen Sie einen Verweis auf den Code hinzu, den Sie bewerten.
  • Installieren Sie das Nuget-Paket von Benchmark.NET mit dem folgenden Befehl (Ansicht> Andere Fenster> Paket-Manager ):
Install-Package BenchmarkDotNet
Code language: PowerShell (powershell)

Am Ende sollte die .csproj-Datei Ihrer Konsolen-App so aussehen:

<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)

Hinweis:In diesem Beispiel bewerte ich Code in einem Projekt namens ThreadQuickSort.

Benchmarks erstellen und ausführen

Mit Benchmark.NET erstellen Sie eine Benchmarking-Klasse. Diese enthält eine oder mehrere Methoden, die mit dem Attribut [Benchmark] gekennzeichnet sind. Wenn Sie die Benchmarks ausführen, werden diese Methoden ausgeführt. Hier fügen Sie auch die Benchmarking-Testdaten hinzu.

Benchmarking-Kurs mit Testdaten erstellen

Um verschiedene Algorithmen zu vergleichen, ist es eine gute Idee, sie mit mehreren Eingabegrößen zu vergleichen. Dies gibt Ihnen auch Auskunft über das tatsächliche Wachstum der Methoden (das mit dem theoretischen Wachstum übereinstimmen sollte, das durch die Big-O-Analyse ermittelt wurde).

Die folgende Benchmark-Klasse ist so konfiguriert, dass sie Diagramme generiert, um die Leistung von drei Sortiermethoden (Array Sort, Fork Join Sort und PLINQ Sort) mit drei Eingabegrößen zu vergleichen:1k, 10k und 100k (wie durch die [Params ] Attribut):

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)

Die mit [GlobalSetup] gekennzeichnete Methode -Attribut wird einmal für jede Eingabegröße ausgeführt. Benchmark-Methoden sollten dieselben Testdaten verwenden und die Originaldaten nicht verändern. Auf diese Weise können Sie einen Äpfel-zu-Äpfel-Vergleich durchführen. Aus diesem Grund generiert es ein zufälliges Array der Größe N und erstellt Kopien des Arrays für jede Benchmark-Methode.

Benchmarks konfigurieren und ausführen

Jetzt, da Sie die Benchmarking-Klasse haben, können Sie sie ausführen, indem Sie BenchmarkRunner verwenden und eine Konfiguration mit den richtigen Exportern zum Generieren von Diagrammen übergeben.

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)

Führen Sie die Benchmarks aus, indem Sie die Konsolen-App ausführen. Dadurch werden die Benchmarks ausgeführt und die Protokollierung an der Konsole gestartet. Es ist sehr ausführlich und es kann eine Weile dauern, bis die Ergebnisse generiert werden.

Sehen Sie sich die Ergebnisse an

Das Ausführen dieser Benchmarks gibt Folgendes an die Konsole aus:

|            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)

Hinweis:Die Zeit wird in Mikrosekunden angegeben, was in der Konsole als „us“ angezeigt wird.

Die Benchmark-Ergebnisse werden auch in folgendes Verzeichnis ausgegeben:\bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\

Es hat die Benchmarks mit Eingabegrößen ausgeführt:1k, 10k und 100k (die mit den Params angegeben wurden Attribut in der Benchmark-Klasse). Es zeigt mehrere Statistiken, gruppiert nach Methodenname und Eingabegröße. Die Ergebnisse können in diesem textbasierten Tabellenformat schwierig zu interpretieren sein. Hier kommen die Grafiken ins Spiel, wie ich gleich zeigen werde.

Diagramme zum Vergleich erstellen

Benchmark.NET generiert Diagramme, indem es die Programmiersprache R verwendet, um die Ergebnisse aus der Datei *-measurements.csv darzustellen. Aus diesem Grund müssen Sie die Exporter CsvMeasurementsExporter und RPlotExporter in der Konfiguration verwenden.

R installieren

Zuerst müssen Sie R.

installieren
  • Besorgen Sie sich die neueste Version von R für Ihr Betriebssystem und installieren Sie sie. (Ich habe Version R-4.1.1-win.exe für Windows installiert)
  • Fügen Sie das \bin\-Verzeichnis von R zur Systemumgebungsvariablen PATH hinzu. (Das bin-Verzeichnis war für mich C:\Program Files\R\R-4.1.1\bin\ )
  • Starten Sie Visual Studio neu, falls es geöffnet war, damit es die aktualisierte PATH-Variable erhält.

Wenn die PATH-Variable nicht ordnungsgemäß aktualisiert wird, wird beim Ausführen der Benchmarks der folgende Fehler angezeigt:

Benchmark.NET erstellt tatsächlich eine R-Skriptdatei namens BuildPlots.R im Build-Ausgabeverzeichnis. Solange Sie über die Datei *-measurements.csv verfügen, können Sie dieses Skript manuell über die Befehlszeile ausführen, wenn Sie möchten. Dies wäre nützlich, wenn Sie die Diagramme nicht jedes Mal generieren möchten, wenn Sie die Benchmarks ausführen:

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

Führen Sie die Benchmarks durch und sehen Sie sich die Grafiken an

Nachdem R installiert ist, führen Sie die Benchmarks erneut aus (indem Sie die Konsolen-App ausführen).

Die resultierenden Grafiken werden hier ausgegeben:\bin\Release\netcoreapp3.1\BenchmarkDotNet.Artifacts\results\.

Es gibt eine große Anzahl von Diagrammbildern. Die Vergleichsgraphen heißen *-barplot und *-boxplot. Sehen Sie sich das *-Balkendiagramm an:

Auf diese Weise können Sie die verschiedenen Sortiermethoden für jede Eingabegröße visuell vergleichen. Die PLINQ-Sortierungsmethode war die schnellste und mehr als doppelt so schnell wie die Array-Sortierungsmethode.

Speichernutzung in Leistungsvergleich einbeziehen

Es ist üblich, beim Vergleich der Leistung hauptsächlich die Ausführungszeit zu betrachten, aber wenn Sie das vollständige Bild erhalten möchten, vergessen Sie nicht, auch die Speichernutzung zu vergleichen.

Um Statistiken zur Speichernutzung einzubeziehen, fügen Sie [MemoryDiagnoser] hinzu Attribut zur Benchmarking-Klasse:

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

Hinweis:Sie können es auch mit AddDiagnoser(MemoryDiagnoser.Default) zur Konfiguration hinzufügen.

Das Ausführen der Benchmarks gibt die folgenden Ergebnisse aus:

|            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)

Hinweis:Der Kürze halber mehrere Spalten entfernt.

Die PLINQSort-Methode ist die schnellste, verbraucht aber auch bei weitem den meisten Speicher (17x mehr als ForkJoinSort).

Dies zeigt, warum es wichtig ist, den Speicher beim Leistungsvergleich nicht zu ignorieren. Es geht darum, das richtige Gleichgewicht zwischen Zeit- und Platzeffizienz zu finden, je nachdem, welchen Ressourcenbeschränkungen Ihre Software in der Produktion ausgesetzt sein wird. Manchmal möchten Sie die schnellste Methode (PLINQSort), manchmal die platzsparendste Methode (ArraySortBaseline), aber meistens möchten Sie den ausgewogenen Ansatz wählen, der schnell genug und relativ platzsparend ist ( ForkJoinSort).