Wrapping dei tempi di StopWatch con un delegato o lambda?


95

Sto scrivendo codice in questo modo, eseguendo un tempismo rapido e sporco:

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

Sicuramente c'è un modo per chiamare questo bit di codice di temporizzazione come un lambda .NET 3.0 di fantasia piuttosto che (Dio non voglia) tagliarlo e incollarlo un paio di volte e sostituire il DoStuff(s)con DoSomethingElse(s)?

So che può essere fatto come un Delegatema mi chiedo il modo lambda.

Risposte:


129

Che ne dici di estendere la classe Stopwatch?

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;
    }
}

Quindi chiamalo in questo modo:

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

Potresti aggiungere un altro overload che ometta il parametro "iterations" e chiama questa versione con un valore predefinito (come 1000).


3
Potresti voler sostituire sw.Start () con sw.StartNew () per evitare di aumentare accidentalmente il tempo trascorso per ogni chiamata consecutiva di s.Time (), riutilizzando la stessa istanza di Stopwatch.
VVS

11
@Jay Sono d'accordo che "foreach" con Enumerable.Range sembra un po 'più "moderno", ma i miei test mostrano che è circa quattro volte più lento di un ciclo "for" su un conteggio elevato. YMMV.
Matt Hamilton

2
-1: L'utilizzo di un'estensione di classe qui non ha senso. Timesi comporta come un metodo statico, scartando tutto lo stato esistente in sw, quindi introdurlo come metodo di istanza sembra semplicemente ingannevole.
ildjarn

2
@ildjam Apprezzo che tu abbia lasciato un commento spiegando il tuo voto negativo, ma penso che tu stia fraintendendo l'idea alla base dei metodi di estensione.
Matt Hamilton

4
@ Matt Hamilton: Non credo, servono per aggiungere (logicamente) metodi di istanza alle classi esistenti. Ma questo non è più un metodo di istanza di Stopwatch.StartNew, che è statico per una ragione. C # non ha la capacità di aggiungere metodi statici alle classi esistenti (a differenza di F #), quindi capisco l'impulso a farlo, ma mi lascia ancora l'amaro in bocca.
ildjarn

31

Ecco cosa ho usato:

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

Utilizzo:

using (new DisposableStopwatch(t => Console.WriteLine("{0} elapsed", t))) {
  // do stuff that I want to measure
}

Questa è la migliore soluzione che abbia mai visto! Nessuna estensione (in modo che possa essere utilizzato su molte classi) e molto pulito!
Calvin

Non sono sicuro di aver ottenuto correttamente l'esempio di utilizzo. Quando provo a usarne alcuni Console.WriteLine("")per testare // do stuff that I want to measure, il compilatore non è affatto contento. Dovresti fare espressioni e dichiarazioni normali lì?
Tim

@Tim - Sono sicuro che l'hai risolto, ma l'istruzione using aveva una parentesi mancante
Alex

12

Potresti provare a scrivere un metodo di estensione per qualunque classe stai utilizzando (o qualsiasi classe base).

Avrei la chiamata simile a:

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

Quindi il metodo di estensione:

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;
}

Qualsiasi oggetto derivante da DependencyObject può ora chiamare TimedFor (..). La funzione può essere facilmente regolata per fornire valori di ritorno tramite i parametri di riferimento.

-

Se non volevi che la funzionalità fosse legata a nessuna classe / oggetto potresti fare qualcosa come:

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;
  }
}

Quindi potresti usarlo come:

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

In caso contrario, questa risposta sembra che abbia qualche discreta capacità "generica":

Wrapping dei tempi di StopWatch con un delegato o lambda?


bello, ma non mi interessa il modo in cui questo è legato a una particolare classe o classe base; può essere fatto più genericamente?
Jeff Atwood,

Come nella classe MyObject per cui è scritto il metodo di estensione? Può essere facilmente modificato per estendere la classe Object o un'altra classe nell'albero dell'ereditarietà.
Mark Ingram,

Stavo pensando più statico, come non legato a NESSUN oggetto o classe particolare .. il tempo e il tempismo sono universali
Jeff Atwood,

Eccellente, la seconda versione è più quello che stavo pensando, +1, ma do il consenso a Matt quando è arrivato per primo.
Jeff Atwood,

7

La StopWatchclasse non deve essere Disposedo Stoppedin errore. Quindi, il codice più semplice per il tempo una certa azione è

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

Codice di chiamata di esempio

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

Non mi piace l'idea di includere le iterazioni nel StopWatchcodice. Puoi sempre creare un altro metodo o estensione che gestisca l'esecuzione delle Niterazioni.

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

Codice di chiamata di esempio

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

Ecco le versioni del metodo di estensione

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

E codice di chiamata di esempio

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

Ho testato i metodi statici e i metodi di estensione (combinando iterazioni e benchmark) e il delta del tempo di esecuzione previsto e del tempo di esecuzione reale è <= 1 ms.


Le versioni del metodo di estensione mi fanno venire l'acquolina in bocca. :)
bzlm

7

Qualche tempo fa ho scritto una semplice classe CodeProfiler che includeva Stopwatch per profilare facilmente un metodo utilizzando un'azione: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Ti consentirà anche di profilare il codice in multithreading facilmente. Il seguente esempio profilerà l'azione lambda con 1-16 thread:

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();
}

4

Supponendo che tu abbia solo bisogno di un rapido tempismo di una cosa, questo è facile da usare.

  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; }
    }
}

2

Puoi sovraccaricare una serie di metodi per coprire vari casi di parametri che potresti voler passare a lambda:

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;
}

In alternativa, puoi usare il delegato Func se devono restituire un valore. È anche possibile passare una matrice (o più) di parametri se ogni iterazione deve utilizzare un valore univoco.


2

Per me l'estensione sembra un po 'più intuitiva su int, non è più necessario istanziare un cronometro o preoccuparsi di resettarlo.

Quindi hai:

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

Con l'utilizzo di esempio di:

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

Output di esempio:

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)


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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.