it-swarm.com.de

Wie messe ich die Code-Performance in .NET?

Ich führe mit DateTime ein ziemlich schnelles und unsauberes Benchmarking in einer einzelnen Zeile C # -Code durch:

long lStart = DateTime.Now.Ticks;
// do something
long lFinish = DateTime.Now.Ticks;

Das Problem liegt in den Ergebnissen:

 Startzeit [633679466564559902] 
 Endzeit [633679466564559902] 
 
 Startzeit [633679466564569917] 
 Endzeit [633679466564569917] 
 
 Startzeit [633679466564579932] 
 Endzeit [633679466564579932] 

...und so weiter.

Da Start- und Zielzeit identisch sind, ist Ticks offensichtlich nicht granular genug.

Wie kann ich die Leistung besser messen?

44
Scott Marlowe

Die seit .NET 2.0 verfügbare Stopwatch-Klasse ist der beste Weg, um dies zu erreichen. Es ist ein sehr leistungsfähiger Zähler, der auf Bruchteile einer Millisekunde genau ist. Schauen Sie sich die MSDN-Dokumentation an, was ziemlich klar ist.

BEARBEITEN: Wie zuvor vorgeschlagen, ist es auch ratsam, Ihren Code mehrmals auszuführen, um eine angemessene Durchschnittszeit zu erhalten.

61
Noldorin

Führen Sie Ihren Code wiederholt aus. Das Problem scheint zu sein, dass Ihr Code viel schneller ausgeführt wird als die Granularität Ihres Messgeräts. Die einfachste Lösung dafür ist, Ihren Code viele, viele Male (Tausende, vielleicht Millionen) auszuführen und dann die durchschnittliche Ausführungszeit zu berechnen.

Edit: Aufgrund der Natur der derzeit verfügbaren Compiler (und virtuellen Maschinen wie CLR und JVM) kann es sehr irreführend sein, die Ausführungsgeschwindigkeit einzelner Codezeilen zu messen, da die Messung die Geschwindigkeit erheblich beeinflussen kann . Ein viel besserer Ansatz wäre, das gesamte System (oder zumindest größere Blöcke) zu profilieren und zu überprüfen, wo die Engpässe liegen.

11
Joachim Sauer

Ich finde diese nützlich

http://accelero.codeplex.com/SourceControl/changeset/view/22633#290971http://accelero.codeplex.com/SourceControl/changeset/view/22633 # 290973http://accelero.codeplex.com/SourceControl/changeset/view/22633#290972

TickTimer ist eine reduzierte Kopie der Stoppuhr, die beim Erstellen gestartet wird und den Neustart nicht unterstützt. Sie werden auch benachrichtigt, wenn die aktuelle Hardware kein hochauflösendes Timing unterstützt (Stoppuhr verschluckt dieses Problem).

Also das 

var tickTimer = new TickTimer();
//call a method that takes some time
DoStuff();
tickTimer.Stop();
Debug.WriteLine("Elapsed HighResElapsedTicks " + tickTimer.HighResElapsedTicks);
Debug.WriteLine("Elapsed DateTimeElapsedTicks " + tickTimer.DateTimeElapsedTicks);
Debug.WriteLine("Elapsed ElapsedMilliseconds " + tickTimer.ElapsedMilliseconds);
Debug.WriteLine("Start Time " + new DateTime(tickTimer.DateTimeUtcStartTicks).ToLocalTime().ToLongTimeString());

wird dies ausgeben

Elapsed HighResElapsedTicks 10022886
Elapsed DateTimeElapsedTicks 41896
Elapsed ElapsedMilliseconds 4.18966178849554
Start Time 11:44:58

DebugTimer ist ein Wrapper für TickTimer, der das Ergebnis in Debug schreibt. (Hinweis: Es unterstützt das Einwegmuster)

Also das

using (new DebugTimer("DoStuff"))
{
    //call a method that takes some time
    DoStuff();
}

wird dies im Debug-Fenster ausgeben

DoStuff: Total 3.6299 ms

IterationDebugTimer gibt an, wie lange es dauert, eine Operation mehrmals auszuführen und das Ergebnis in Debug zu schreiben. Es führt auch einen ersten Start durch, der nicht enthalten ist, um die Startzeit zu ignorieren. (Hinweis: Es unterstützt das Einwegmuster)

Also das

int x;
using (var iterationDebugTimer = new IterationDebugTimer("Add", 100000))
{
    iterationDebugTimer.Run(() =>
    {
        x = 1+4;
    });
}

Wird dies ausgeben

Add: Iterations 100000 
Total 1.198540 ms 
Single 0.000012 ms
8
Simon

Nur um das zu ergänzen, was andere bereits über die Verwendung von Stoppuhren und das Messen von Durchschnittswerten gesagt haben.

Stellen Sie sicher, dass Sie Ihre Methode vor dem Messen aufrufen. Andernfalls messen Sie die Zeit, die zum Kompilieren des Codes durch JIT erforderlich ist. Das kann Ihre Zahlen erheblich verzerren.

Stellen Sie außerdem sicher, dass Sie den Code für den Freigabemodus messen, da Optimierungen für Debugbuilds standardmäßig deaktiviert sind. Tuning Debug Code ist imho sinnlos. 

Und stellen Sie sicher, dass Sie messen, was Sie tatsächlich messen möchten. Wenn Optimierungen eingesetzt werden, kann der Compiler/JIT-Compiler den Code neu anordnen oder vollständig entfernen, sodass Sie am Ende etwas messen können, das ein wenig anders ist als beabsichtigt. Sehen Sie sich mindestens den generierten Code an, um sicherzustellen, dass Code nicht entfernt wurde.

Je nachdem, was Sie zu messen versuchen, denken Sie daran, dass ein echtes System die Laufzeit anders belastet als eine typische Testanwendung. Einige Leistungsprobleme betreffen z. wie Objekte Müll gesammelt werden. Diese Probleme werden normalerweise in einer einfachen Testanwendung nicht angezeigt. 

Tatsächlich ist es am besten, reale Systeme mit realen Daten zu messen, da Sandbox-Tests sehr ungenau sein können. 

8
Brian Rasmussen

Verwenden Sie einen echten Profiler wie dotTrace.

4
Otávio Décio

Sie können die Variable Stopwatch verwenden, sofern Sie .NET 2.0 oder neuer verwenden.

System.Diagnostics.Stopwatch.StartNew();

Die Stopwatch-Klasse hat auch ein öffentliches schreibgeschütztes Feld IsHighResolution , das Sie darüber informiert, ob die Stoppuhr auf einem Leistungszähler mit hoher Auflösung basiert. Wenn nicht, basiert es auf dem Systemzeitgeber.

Ich bin mir nicht sicher, was für eine Stoppuhr erforderlich ist, um auf einen hochauflösenden Leistungszähler zu setzen. Es gibt einige API-Aufrufe, aber ich stelle fest, wenn die Stoppuhr keine hohe Auflösung verwendet, ist die API wahrscheinlich nicht vorhanden.

4
JoshBerke

Beispiel für die Stopwatch-Klasse

    using System.Diagnostics;
    ......
    ...
    ..
    Stopwatch sw = new Stopwatch();
    sw.Start();
    //Your Code Here

    sw.Stop();
    Console.WriteLine("Elapsed={0}",sw.Elapsed);
4
WhTiggaW

Siehe die Antwort auf Ist DateTime.Now der beste Weg, um die Leistung einer Funktion zu messen? für eine Erklärung oder lies meinen Blogbeitrag über Hochleistungsmessung

Das Problem ist, dass DateTime eine Auflösung von etwa 15 ms hat, es kann nicht genauer sein. Stoppuhr kann jedoch.

3
Markus Olsson

Hier ein nettes Schreiben an der MSDN, wie man einen fortlaufend aktualisierenden, hochauflösenden Zeitanbieter für Windows implementiert

Hier ist der Beispielquellcode für den Artikel (C++).

3
Patrick Cuff

https://andreyakinshin.gitbooks.io/performancebookdotnet/content/science/microbenchmarking.html

https://github.com/PerfDotNet/BenchmarkDotNet

"In der Tat ist das Mikrobenmarkieren sehr schwierig. Wenn eine Operation 10–100 ns dauert, ist die Operationsmessung eine große Herausforderung. Ich empfehle Ihnen, BenchmarkDotNet für Ihre Benchmarks zu verwenden. Es ist eine Bibliothek, die Ihnen helfen kann, ehrliche Benchmarks zu erstellen und Messungen durchzuführen Gute Präzision. Natürlich können Sie auch ohne zusätzliche Bibliotheken einen eigenen Benchmark schreiben. In diesem Abschnitt sprechen wir darüber, warum es wahrscheinlich eine schlechte Idee ist und was Sie vor dem Start wissen sollten. "

3
Evgeniy

Eine andere Möglichkeit ist, den Timer-Code automatisch mit Fody einzufügen. Dies macht Ihren Code viel einfacher zu lesen, da er Ihre Querschnittsprobleme voneinander trennt. Ich denke, das ist nahe an dem, was aspektorientierte Programmierung heißt, aber nach der Kompilierzeit gemacht. 

Siehe https://github.com/Fody/MethodTimer für das Fody-Addon, das das Timing der Methode ausführt. 

Zitieren aus der Liesmich:

Mit einem Abfangjäger irgendwo in Ihrer Assembly:  

public static class MethodTimeLogger {
  public static void Log(MethodBase methodBase, long milliseconds)
  {
    //Do some logging here
  } 
}

Ihr Code,

public class MyClass
{
    [Time]
    public void MyMethod()
    {
        //Some code u are curious how long it takes
        Console.WriteLine("Hello");
    }
}

wird dazu kompiliert:

public class MyClass
{
    public void MyMethod()
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            //Some code u are curious how long it takes
            Console.WriteLine("Hello");
        }
        finally
        {
            stopwatch.Stop();
            MethodTimeLogger.Log(methodof(MyClass.MyMethod), stopwatch.ElapsedMilliseconds);
        }
    }
}
1
Meirion Hughes

Einwegart Stopwatch funktioniert am besten für mich.

class VWatch : IDisposable {
    Stopwatch watch = new Stopwatch();
    public VWatch() {
        this.watch.Start();
    }
    public void Dispose() {
        this.watch.Stop();
        Console.WriteLine("Finished. Elapsed={0}", this.watch.Elapsed);
    }
}

Und dann:

using (new VWatch()) {
    /// do something for time measurement
}
1
Val

Meine Vorlieben gelten für BenchmarkDotNet von @Evgeniy. Wahrscheinlich wird seine Antwort umgangen, weil es kein Code-Snippet gibt, aber da es sich um ein komplexes Element handelt, lohnt es sich zumindest, vorher einen Blick in die Bibliothek zu werfen, um zuerst auf etwas Maßgeschneidertes einzugehen.

Und weil ein Code immer auffällt, ist hier das Beispiel von der zitierten Seite:

using System;
using System.Security.Cryptography;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace MyBenchmarks
{
    [ClrJob(baseline: true), CoreJob, MonoJob, CoreRtJob]
    [RPlotExporter, RankColumn]
    public class Md5VsSha256
    {
        private SHA256 sha256 = SHA256.Create();
        private MD5 md5 = MD5.Create();
        private byte[] data;

        [Params(1000, 10000)]
        public int N;

        [GlobalSetup]
        public void Setup()
        {
            data = new byte[N];
            new Random(42).NextBytes(data);
        }

        [Benchmark]
        public byte[] Sha256() => sha256.ComputeHash(data);

        [Benchmark]
        public byte[] Md5() => md5.ComputeHash(data);
    }

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

Dieser Code-Projektartikel zeigt, wie Sie mit dem Hochleistungstimer die Ausführungsgeschwindigkeit Ihres Codes messen können:

http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx

Hier finden Sie eine Reihe von Open Source C # -Profilern:

http://csharp-source.net/open-source/profilers

1
Ashley Davis

Ich habe eine Erweiterung erstellt, die Millisekunden von Ticks zurückgibt. 

public static int GetTotalRunningTimeInMilliseconds(this DateTime start)
{
    var endTicks = DateTime.Now.Ticks - start.Ticks;
    return TimeSpan.FromTicks(endTicks).Milliseconds;
}

Verwendungszweck:

 var start = DateTime.Now;

 //...your long running code here

 var endTime = start.GetTotalRunningTimeInMilliseconds();
0
Amc_rtty

Manchmal ist es vielleicht am besten zu sehen, warum Sie die Operation zeitlich festlegen müssen. Läuft es langsam? Oder bist du nur neugierig? Die erste Regel der Optimierung lautet "Nicht machen". Abhängig davon, was Sie tatsächlich messen, kann sich die Ansicht ändern, welches Werkzeug für die Aufgabe am besten geeignet ist.

0

Ich habe eine sehr einfache Methode erstellt, mit der die Ausführungsgeschwindigkeit einer Action gemessen wird. Dies hat für mich den Vorteil, dass ich es jederzeit wiederverwenden kann, und welchen Code ich auch immer messen muss.

Für mich war eine DateTime genug, aber sie lässt sich leicht von DateTime auf Stopwatch anpassen.

public static TimeSpan MeasureTime(Action action)
{
    DateTime start = DateTime.Now;

    if (action == null)
    {
        throw new ArgumentNullException("action");
    }

    try
    {
        action();
    }
    catch (Exception ex)
    {
        Debugger.Log(1, "Measuring",ex.ToString());
    }

    return DateTime.Now - start;
}

Wie benutzt man es ?:

private static void StressTest()
{
    List<TimeSpan> tss = new List<TimeSpan>();

    for (int i = 0; i < 100; i++)
    {
        // here is the measuring:
        var ts = MeasureTime(() => instance.Method("param1"));

        tss.Add(ts);
    }

    Console.WriteLine("Max: {0}", tss.Max());
    Console.WriteLine("Min: {0}", tss.Min());
    Console.WriteLine("Avg: {0}", TimeSpan.FromMilliseconds(tss.Average(i => i.TotalMilliseconds)));
}

oder:

var ts = MeasureTime(() =>
                        {
                            // Some intensive stuff here
                            int a = 1;

                            // more here
                            int b = 2;

                            // and so on
                        });
0
coloboxp

Am einfachsten ist es, einen Profiler wie ANTS Performance Profiler oder einen der anderen verfügbaren zu verwenden.

0
Ben Emmett