StopWatch-Timing mit einem Delegierten oder Lambda einpacken?

95

Ich schreibe Code wie diesen und mache ein wenig schnelles und schmutziges Timing:

var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
    b = DoStuff(s);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

Sicherlich gibt es eine Möglichkeit , dieses Stück Zeitcode als fancy-schmancy .NET 3.0 Lambda zu nennen , anstatt (Gott bewahre) schneiden und es ein paar Mal einfügen und ersetzt die DoStuff(s)mit DoSomethingElse(s)?

Ich weiß, dass es so gemacht werden kann, Delegateaber ich wundere mich über den Lambda-Weg.

Jeff Atwood
quelle

Antworten:

129

Wie wäre es mit einer Erweiterung der Stoppuhrklasse?

public static class StopwatchExtensions
{
    public static long Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Reset();
        sw.Start(); 
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.ElapsedMilliseconds;
    }
}

Dann nenne es so:

var s = new Stopwatch();
Console.WriteLine(s.Time(() => DoStuff(), 1000));

Sie können eine weitere Überladung hinzufügen, bei der der Parameter "Iterationen" weggelassen wird und diese Version mit einem Standardwert (z. B. 1000) aufgerufen wird.

Matt Hamilton
quelle
3
Möglicherweise möchten Sie sw.Start () durch sw.StartNew () ersetzen, um zu verhindern, dass die verstrichene Zeit für jeden aufeinanderfolgenden Aufruf von s.Time () versehentlich erhöht wird, und dieselbe Stoppuhrinstanz wiederverwenden.
VVS
11
@ Jay Ich stimme zu, dass "foreach" mit Enumerable.Range etwas "moderner" aussieht, aber meine Tests zeigen, dass es über eine große Anzahl ungefähr viermal langsamer ist als eine "for" -Schleife. YMMV.
Matt Hamilton
2
-1: Die Verwendung einer Klassenerweiterung macht hier keinen Sinn. Timeverhält sich wie eine statische Methode, bei der der gesamte vorhandene Status swverworfen wird. Die Einführung als Instanzmethode sieht also nur unübersichtlich aus.
ildjarn
2
@ildjam Ich weiß es zu schätzen, dass Sie einen Kommentar hinterlassen haben, in dem Ihre Ablehnung erklärt wird, aber ich denke, Sie verstehen die Idee hinter den Erweiterungsmethoden falsch.
Matt Hamilton
4
@ Matt Hamilton: Ich glaube nicht - sie dienen zum Hinzufügen (logisch) Instanzmethoden zu vorhandenen Klassen. Dies ist jedoch nicht mehr eine Instanzmethode als Stopwatch.StartNew, die aus einem bestimmten Grund statisch ist. C # ist nicht in der Lage, vorhandenen Klassen statische Methoden hinzuzufügen (im Gegensatz zu F #), daher verstehe ich den Impuls, dies zu tun, aber es hinterlässt immer noch einen schlechten Geschmack in meinem Mund.
ildjarn
31

Folgendes habe ich verwendet:

public class DisposableStopwatch: IDisposable {
    private readonly Stopwatch sw;
    private readonly Action<TimeSpan> f;

    public DisposableStopwatch(Action<TimeSpan> f) {
        this.f = f;
        sw = Stopwatch.StartNew();
    }

    public void Dispose() {
        sw.Stop();
        f(sw.Elapsed);
    }
}

Verwendung:

using (new DisposableStopwatch(t => Console.WriteLine("{0} elapsed", t))) {
  // do stuff that I want to measure
}
Mauricio Scheffer
quelle
Dies ist die beste Lösung, die ich je gesehen habe! Keine Erweiterung (so dass es in vielen Klassen verwendet werden kann) und sehr sauber!
Calvin
Ich bin mir nicht sicher, ob ich das Anwendungsbeispiel richtig verstanden habe. Wenn ich versuche, einige Console.WriteLine("")zum Testen unter zu verwenden // do stuff that I want to measure, ist der Compiler überhaupt nicht glücklich. Sollen Sie dort normale Ausdrücke und Aussagen machen?
Tim
@ Tim - Ich bin sicher, Sie haben es geschafft, aber die using-Anweisung hatte eine fehlende Klammer
Alex
12

Sie können versuchen, eine Erweiterungsmethode für die von Ihnen verwendete Klasse (oder eine beliebige Basisklasse) zu schreiben.

Ich würde den Anruf so aussehen lassen:

Stopwatch sw = MyObject.TimedFor(1000, () => DoStuff(s));

Dann die Erweiterungsmethode:

public static Stopwatch TimedFor(this DependencyObject source, Int32 loops, Action action)
{
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < loops; ++i)
{
    action.Invoke();
}
sw.Stop();

return sw;
}

Jedes von DependencyObject abgeleitete Objekt kann jetzt TimedFor (..) aufrufen. Die Funktion kann einfach angepasst werden, um Rückgabewerte über ref-Parameter bereitzustellen.

- -

Wenn Sie nicht möchten, dass die Funktionalität an eine Klasse / ein Objekt gebunden wird, können Sie Folgendes tun:

public class Timing
{
  public static Stopwatch TimedFor(Action action, Int32 loops)
  {
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < loops; ++i)
    {
      action.Invoke();
    }
    sw.Stop();

    return sw;
  }
}

Dann könnten Sie es wie folgt verwenden:

Stopwatch sw = Timing.TimedFor(() => DoStuff(s), 1000);

Andernfalls scheint diese Antwort eine anständige "generische" Fähigkeit zu haben:

StopWatch-Timing mit einem Delegierten oder Lambda einpacken?

Mark Ingram
quelle
cool, aber es interessiert mich nicht, wie dies an eine bestimmte Klasse oder Basisklasse gebunden ist; kann es allgemeiner gemacht werden?
Jeff Atwood
Wie in der MyObject-Klasse, für die die Erweiterungsmethode geschrieben wurde? Es kann leicht geändert werden, um die Object-Klasse oder eine andere Klasse im Vererbungsbaum zu erweitern.
Mark Ingram
Ich dachte statischer, als wäre ich nicht an ein bestimmtes Objekt oder eine bestimmte Klasse gebunden. Zeit und Timing sind irgendwie universell
Jeff Atwood
Ausgezeichnet, die 2. Version ist mehr als ich dachte, +1, aber ich akzeptiere Matt, als er zuerst dazu kam.
Jeff Atwood
7

Die StopWatchKlasse muss nicht Disposedoder Stoppedfehlerhaft sein. So ist die einfachste Code zu Zeit einige Aktion ist

public partial class With
{
    public static long Benchmark(Action action)
    {
        var stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

Beispiel für einen Anrufcode

public void Execute(Action action)
{
    var time = With.Benchmark(action);
    log.DebugFormat(“Did action in {0} ms.”, time);
}

Ich mag die Idee nicht, die Iterationen in den StopWatchCode aufzunehmen. Sie können jederzeit eine andere Methode oder Erweiterung erstellen, die die Ausführung von NIterationen übernimmt .

public partial class With
{
    public static void Iterations(int n, Action action)
    {
        for(int count = 0; count < n; count++)
            action();
    }
}

Beispiel für einen Anrufcode

public void Execute(Action action, int n)
{
    var time = With.Benchmark(With.Iterations(n, action));
    log.DebugFormat(“Did action {0} times in {1} ms.”, n, time);
}

Hier sind die Versionen der Erweiterungsmethoden

public static class Extensions
{
    public static long Benchmark(this Action action)
    {
        return With.Benchmark(action);
    }

    public static Action Iterations(this Action action, int n)
    {
        return () => With.Iterations(n, action);
    }
}

Und Beispiel für einen Anrufcode

public void Execute(Action action, int n)
{
    var time = action.Iterations(n).Benchmark()
    log.DebugFormat(“Did action {0} times in {1} ms.”, n, time);
}

Ich habe die statischen Methoden und Erweiterungsmethoden (Kombination von Iterationen und Benchmark) getestet und das Delta der erwarteten Ausführungszeit und der tatsächlichen Ausführungszeit beträgt <= 1 ms.

Anthony Mastrean
quelle
Die Versionen der Verlängerungsmethode machen mir das Wasser im Mund zusammen. :)
bzlm
7

Ich habe vor einiger Zeit eine einfache CodeProfiler-Klasse geschrieben, die Stopwatch umhüllte, um eine Methode mithilfe einer Aktion einfach zu profilieren: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Außerdem können Sie den Multithread-Code problemlos profilieren. Im folgenden Beispiel wird die Aktion Lambda mit 1-16 Threads dargestellt:

static void Main(string[] args)
{
    Action action = () =>
    {
        for (int i = 0; i < 10000000; i++)
            Math.Sqrt(i);
    };

    for(int i=1; i<=16; i++)
        Console.WriteLine(i + " thread(s):\t" + 
            CodeProfiler.ProfileAction(action, 100, i));

    Console.Read();
}
Mark S. Rasmussen
quelle
4

Angenommen, Sie benötigen nur ein schnelles Timing für eine Sache, die einfach zu verwenden ist.

  public static class Test {
    public static void Invoke() {
        using( SingleTimer.Start )
            Thread.Sleep( 200 );
        Console.WriteLine( SingleTimer.Elapsed );

        using( SingleTimer.Start ) {
            Thread.Sleep( 300 );
        }
        Console.WriteLine( SingleTimer.Elapsed );
    }
}

public class SingleTimer :IDisposable {
    private Stopwatch stopwatch = new Stopwatch();

    public static readonly SingleTimer timer = new SingleTimer();
    public static SingleTimer Start {
        get {
            timer.stopwatch.Reset();
            timer.stopwatch.Start();
            return timer;
        }
    }

    public void Stop() {
        stopwatch.Stop();
    }
    public void Dispose() {
        stopwatch.Stop();
    }

    public static TimeSpan Elapsed {
        get { return timer.stopwatch.Elapsed; }
    }
}
jyoung
quelle
2

Sie können eine Reihe von Methoden überladen, um verschiedene Fälle von Parametern abzudecken, die Sie möglicherweise an das Lambda übergeben möchten:

public static Stopwatch MeasureTime<T>(int iterations, Action<T> action, T param)
{
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
    {
        action.Invoke(param);
    }
    sw.Stop();

    return sw;
}

public static Stopwatch MeasureTime<T, K>(int iterations, Action<T, K> action, T param1, K param2)
{
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
    {
        action.Invoke(param1, param2);
    }
    sw.Stop();

    return sw;
}

Alternativ können Sie den Func-Delegaten verwenden, wenn er einen Wert zurückgeben muss. Sie können auch ein Array (oder mehrere) von Parametern übergeben, wenn für jede Iteration ein eindeutiger Wert verwendet werden muss.

Morten Christiansen
quelle
2

Für mich fühlt sich die Erweiterung auf int etwas intuitiver an. Sie müssen keine Stoppuhr mehr instanziieren oder sich Gedanken über das Zurücksetzen machen.

Also hast du:

static class BenchmarkExtension {

    public static void Times(this int times, string description, Action action) {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < times; i++) {
            action();
        }
        watch.Stop();
        Console.WriteLine("{0} ... Total time: {1}ms ({2} iterations)", 
            description,  
            watch.ElapsedMilliseconds,
            times);
    }
}

Mit der Beispielverwendung von:

var randomStrings = Enumerable.Range(0, 10000)
    .Select(_ => Guid.NewGuid().ToString())
    .ToArray();

50.Times("Add 10,000 random strings to a Dictionary", 
    () => {
        var dict = new Dictionary<string, object>();
        foreach (var str in randomStrings) {
            dict.Add(str, null);
        }
    });

50.Times("Add 10,000 random strings to a SortedList",
    () => {
        var list = new SortedList<string, object>();
        foreach (var str in randomStrings) {
            list.Add(str, null);
        }
    });

Beispielausgabe:

Add 10,000 random strings to a Dictionary ... Total time: 144ms (50 iterations)
Add 10,000 random strings to a SortedList ... Total time: 4088ms (50 iterations)
Sam Safran
quelle
1

Ich verwende gerne die CodeTimer-Klassen von Vance Morrison (einer der Performance-Typen von .NET).

Er schrieb einen Beitrag in seinem Blog mit dem Titel " Verwalteten Code schnell und einfach messen: CodeTimers ".

Es enthält coole Sachen wie einen MultiSampleCodeTimer. Es berechnet automatisch den Mittelwert und die Standardabweichung und ist auch sehr einfach, Ihre Ergebnisse auszudrucken.

Davy Landman
quelle
0
public static class StopWatchExtensions
{
    public static async Task<TimeSpan> LogElapsedMillisecondsAsync(
        this Stopwatch stopwatch,
        ILogger logger,
        string actionName,
        Func<Task> action)
    {
        stopwatch.Reset();
        stopwatch.Start();

        await action();

        stopwatch.Stop();

        logger.LogDebug(string.Format(actionName + " completed in {0}.", stopwatch.Elapsed.ToString("hh\\:mm\\:ss")));

        return stopwatch.Elapsed;
    }
}
Alper Ebicoglu
quelle