web-dev-qa-db-fra.com

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

J'écris du code comme ça, en 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 bit de code de synchronisation comme un lambda de fantaisie-schmancy .NET 3.0 plutôt que (Dieu nous en préserve) de le couper et de le coller plusieurs fois et de remplacer le DoStuff(s) avec DoSomethingElse(s)?

Je sais que cela peut être fait en tant que Delegate mais je me pose des questions sur la méthode lambda.

94
Jeff Atwood

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

Ensuite, 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).

128
Matt Hamilton

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

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

Je voudrais que l'appel ressemble à:

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

Ensuite, 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 facilement être ajustée pour fournir des valeurs de retour via les paramètres ref.

-

Si vous ne vouliez pas que la fonctionnalité soit liée à une classe/un objet, vous pourriez 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);

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

Emballage chronométrage chronomètre avec un délégué ou lambda?

12
Mark Ingram

La classe StopWatch n'a pas besoin d'être Disposed ou Stopped en cas d'erreur. Ainsi, le code le plus simple pour temps certains 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 code StopWatch. Vous pouvez toujours créer une autre méthode ou extension qui gère l'exécution des itérations N.

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 des méthodes 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 d'exécution réel est <= 1 ms.

7
Anthony Mastrean

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

Il 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();
}
7
Mark S. Rasmussen

En supposant que vous ayez juste besoin d'un timing rapide d'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; }
    }
}
4
jyoung

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'il doit renvoyer une valeur. Vous pouvez également transmettre un tableau (ou plusieurs) 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 ni de vous soucier de le réinitialiser.

Vous avez donc:

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)
2
Sam Saffron

J'aime utiliser les classes CodeTimer de Vance Morrison (l'un des mecs de la performance de .NET).

Il a publié un article sur son blog intitulé " Mesurer rapidement et facilement le code managé: CodeTimers ".

Il comprend des trucs sympas comme un MultiSampleCodeTimer. Il calcule automatiquement la moyenne et l'écart type et il est également très facile d'imprimer vos résultats.

1
Davy Landman
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;
    }
}
0
Alper Ebicoglu