Emballage du chronométrage StopWatch avec un délégué ou un lambda?


95

J'écris du code comme celui-ci, faisant un petit timing rapide et sale:

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

Il y a sûrement un moyen d'appeler ce morceau de code de synchronisation comme un lambda de fantaisie .NET 3.0 plutôt que (Dieu nous en préserve) de le couper et de le coller plusieurs fois et de le remplacer DoStuff(s)par DoSomethingElse(s)?

Je sais que cela peut être fait comme un Delegatemais je m'interroge sur la manière lambda.

Réponses:


129

Que diriez-vous d'étendre la classe Chronomètre?

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

Alors appelez-le comme ceci:

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

Vous pouvez ajouter une autre surcharge qui omet le paramètre "itérations" et appelle cette version avec une valeur par défaut (comme 1000).


3
Vous pouvez remplacer sw.Start () par sw.StartNew () pour éviter d'augmenter accidentellement le temps écoulé pour chaque appel consécutif de s.Time (), en réutilisant la même instance de Stopwatch.
VVS

11
@Jay Je suis d'accord que "foreach" avec Enumerable.Range a l'air un peu plus "moderne", mais mes tests montrent que c'est environ quatre fois plus lent qu'une boucle "for" sur un grand nombre. YMMV.
Matt Hamilton

2
-1: Utiliser une extension de classe ici n'a aucun sens. Timese comporte comme une méthode statique, supprimant tout état existant dans sw, donc l'introduire en tant que méthode d'instance semble simplement gadget.
ildjarn

2
@ildjam J'apprécie que vous ayez laissé un commentaire expliquant votre vote négatif, mais je pense que vous ne comprenez pas l'idée derrière les méthodes d'extension.
Matt Hamilton

4
@Matt Hamilton: Je ne pense pas - ils servent à ajouter (logiquement) des méthodes d'instance aux classes existantes. Mais ce n'est pas plus une méthode d'instance que Stopwatch.StartNew, ce qui est statique pour une raison. C # n'a pas la possibilité d'ajouter des méthodes statiques aux classes existantes (contrairement à F #), donc je comprends l'impulsion de le faire, mais cela laisse toujours un mauvais goût dans ma bouche.
ildjarn

31

Voici ce que j'utilise:

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

Usage:

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

C'est la meilleure solution que j'aie jamais vue! Aucune extension (pour qu'elle puisse être utilisée sur de nombreuses classes) et très propre!
Calvin

Je ne sais pas si j'ai correctement obtenu l'exemple d'utilisation. Quand j'essaye d'en utiliser Console.WriteLine("")pour tester sous // do stuff that I want to measure, alors le compilateur n'est pas du tout content. Êtes-vous censé y faire des expressions et des déclarations normales?
Tim

@Tim - Je suis sûr que vous l'avez résolu, mais l'instruction using avait un crochet manquant
Alex

12

Vous pouvez essayer d'écrire une méthode d'extension pour la classe que vous utilisez (ou pour toute classe de base).

Je voudrais que l'appel ressemble à:

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

Puis la méthode d'extension:

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

Tout objet dérivant de DependencyObject peut désormais appeler TimedFor (..). La fonction peut être facilement ajustée pour fournir des valeurs de retour via les paramètres de référence.

-

Si vous ne voulez pas que la fonctionnalité soit liée à une classe / un objet, vous pouvez faire quelque chose comme:

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

Ensuite, vous pouvez l'utiliser comme:

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

À défaut, cette réponse semble avoir une capacité "générique" décente:

Emballage du chronométrage StopWatch avec un délégué ou un lambda?


cool, mais je me fiche de la façon dont cela est lié à une classe particulière ou à une classe de base; cela peut-il être fait de manière plus générique?
Jeff Atwood

Comme dans la classe MyObject pour laquelle la méthode d'extension est écrite? Il peut facilement être modifié pour étendre la classe Object ou une autre classe dans l'arborescence d'héritage.
Mark Ingram

Je pensais plus statique, comme ne pas être lié à AUCUN objet ou à une classe en particulier ... le temps et le timing sont en quelque sorte universels
Jeff Atwood

Excellent, la 2ème version est plus ce que je pensais, +1, mais je donne accepté à Matt car il y est arrivé le premier.
Jeff Atwood

7

La StopWatchclasse n'a pas besoin d'être Disposedou Stoppedsur erreur. Ainsi, le code le plus simple pour chronométrer une action est

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

Exemple de code d'appel

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

Je n'aime pas l'idée d'inclure les itérations dans le StopWatchcode. Vous pouvez toujours créer une autre méthode ou une extension qui gère l'exécution des Nitérations.

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

Exemple de code d'appel

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

Voici les versions de la méthode d'extension

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

Et un exemple de code d'appel

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

J'ai testé les méthodes statiques et les méthodes d'extension (combinant itérations et benchmark) et le delta du temps d'exécution attendu et du temps réel d'exécution est <= 1 ms.


Les versions de la méthode d'extension me mettent l'eau à la bouche. :)
bzlm

7

J'ai écrit il y a quelque temps une simple classe CodeProfiler qui enveloppait Stopwatch pour profiler facilement une méthode à l'aide d'une action: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Cela vous permettra également de profiler facilement le code multithread. L'exemple suivant profilera l'action lambda avec 1 à 16 threads:

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

En supposant que vous ayez juste besoin d'un timing rapide pour une chose, c'est facile à utiliser.

  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

Vous pouvez surcharger un certain nombre de méthodes pour couvrir divers cas de paramètres que vous voudrez peut-être passer au 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;
}

Vous pouvez également utiliser le délégué Func s'ils doivent renvoyer une valeur. Vous pouvez également transmettre un tableau (ou plus) de paramètres si chaque itération doit utiliser une valeur unique.


2

Pour moi, l'extension semble un peu plus intuitive sur int, vous n'avez plus besoin d'instancier un chronomètre ou de vous soucier de le réinitialiser.

Donc vous avez:

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

Avec l'exemple d'utilisation de:

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

Exemple de sortie:

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;
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.