116 Stimmen

Benchmarking kleiner Codebeispiele in C#, kann diese Implementierung verbessert werden?

Bei SO ertappe ich mich oft dabei, dass ich kleine Codeabschnitte vergleiche, um zu sehen, welche Implementierung am schnellsten ist.

Oft sehe ich Kommentare, dass der Benchmarking-Code das Jitting oder den Garbage Collector nicht berücksichtigt.

Ich habe die folgende einfache Benchmarking-Funktion, die ich langsam weiterentwickelt habe:

  static void Profile(string description, int iterations, Action func) {
        // warm up 
        func();
        // clean up
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }

Verwendung:

Profile("a descriptions", how_many_iterations_to_run, () =>
{
   // ... code being profiled
});

Weist diese Umsetzung irgendwelche Mängel auf? Ist sie gut genug, um zu zeigen, dass Implementierung X über Z Iterationen schneller ist als Implementierung Y? Fällt Ihnen etwas ein, was Sie verbessern könnten?

EDIT Seine ziemlich klar, dass eine Zeit-basierten Ansatz (im Gegensatz zu Iterationen), bevorzugt wird, hat jemand alle Implementierungen, wo die Zeitprüfungen nicht Auswirkungen Leistung?

0 Stimmen

Siehe auch BenchmarkDotNet .

104voto

Sam Saffron Punkte 124121

Hier ist die geänderte Funktion: wie von der Gemeinschaft empfohlen, fühlen Sie sich frei, dies zu ändern es ist ein Gemeinschafts-Wiki.

static double Profile(string description, int iterations, Action func) {
    //Run at highest priority to minimize fluctuations caused by other processes/threads
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
    Thread.CurrentThread.Priority = ThreadPriority.Highest;

    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
    return watch.Elapsed.TotalMilliseconds;
}

Stellen Sie sicher, dass Sie Kompilieren Sie in Release mit aktivierten Optimierungen, und führen Sie die Tests außerhalb von Visual Studio aus. . Dieser letzte Teil ist wichtig, weil das JIT seine Optimierungen mit einem angeschlossenen Debugger einschränkt, selbst im Release-Modus.

24voto

LukeH Punkte 251752

Die Fertigstellung wird nicht unbedingt vor GC.Collect zurück. Die Finalisierung wird in eine Warteschlange gestellt und dann in einem separaten Thread ausgeführt. Dieser Thread könnte während Ihrer Tests noch aktiv sein und die Ergebnisse beeinflussen.

Wenn Sie sich vergewissern wollen, dass die Fertigstellung abgeschlossen ist, bevor Sie mit Ihren Tests beginnen, sollten Sie GC.WaitForPendingFinalizers die so lange blockiert wird, bis die Warteschlange für den Abschluss geleert ist:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

16voto

Jonathan Rupp Punkte 15162

Wenn Sie GC-Interaktionen aus der Gleichung herausnehmen wollen, sollten Sie Ihren "Warm-up"-Aufruf durchführen après dem GC.Collect-Aufruf, nicht vorher. Auf diese Weise wissen Sie, dass .NET bereits genügend Speicher vom Betriebssystem für die Arbeitsmenge Ihrer Funktion zugewiesen hat.

Denken Sie daran, dass Sie bei jeder Iteration einen nicht untergeordneten Methodenaufruf machen, also stellen Sie sicher, dass Sie die Dinge, die Sie testen, mit einem leeren Körper vergleichen. Sie müssen sich auch damit abfinden, dass Sie nur Dinge, die um ein Vielfaches länger sind als ein Methodenaufruf, zuverlässig messen können.

Abhängig von der Art des Materials, das Sie profilieren, können Sie auch Ihr Timing basierend auf einer bestimmten Zeitspanne anstatt einer bestimmten Anzahl von Iterationen durchführen wollen -- dies kann zu leichter vergleichbaren Zahlen führen, ohne dass Sie einen sehr kurzen Lauf für die beste Implementierung und/oder einen sehr langen für die schlechteste haben müssen.

7voto

Paul Alexander Punkte 31302

Ich denke, das schwierigste Problem bei Benchmarking-Methoden wie dieser ist die Berücksichtigung von Sonderfällen und Unvorhergesehenem. Zum Beispiel: "Wie funktionieren die beiden Codeschnipsel unter hoher CPU-Last/Netzwerknutzung/Festplattenauslastung usw.? Sie eignen sich hervorragend für grundlegende Logikprüfungen, um festzustellen, ob ein bestimmter Algorithmus funktioniert. deutlich schneller als ein anderer. Aber um die Leistung der meisten Codes richtig zu testen, müssten Sie einen Test erstellen, der die spezifischen Engpässe des jeweiligen Codes misst.

Ich würde immer noch sagen, dass das Testen von kleinen Codeblöcken oft wenig Gewinn bringt und dazu führen kann, dass zu komplexer Code anstelle von einfachem, wartbarem Code verwendet wird. Das Schreiben von klarem Code, den andere Entwickler oder ich selbst in 6 Monaten schnell verstehen können, bringt mehr Leistungsvorteile als hoch optimierter Code.

6voto

Alex Yakunin Punkte 6000

Ich würde es vermeiden, den Delegierten überhaupt zu übergeben:

  1. Delegate call ist ~ virtueller Methodenaufruf. Nicht billig: ~ 25% der kleinsten Speicherzuweisung in .NET. Wenn Sie an Details interessiert sind, siehe z.B. dieser Link .
  2. Anonyme Delegierte können zur Verwendung von Verschlüssen führen, die Sie nicht einmal bemerken. Auch hier ist der Zugriff auf Closure-Felder spürbar anders als z.B. der Zugriff auf eine Variable auf dem Stack.

Ein Beispielcode, der zur Verwendung des Verschlusses führt:

public void Test()
{
  int someNumber = 1;
  Profiler.Profile("Closure access", 1000000, 
    () => someNumber + someNumber);
}

Wenn Sie sich mit Closures nicht auskennen, sehen Sie sich diese Methode in .NET Reflector an.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X