Zawijanie czasu StopWatch z delegatem lub lambdą?


96

Piszę kod w ten sposób, robiąc szybki i brudny czas:

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

Z pewnością istnieje sposób, aby wywołać ten kawałek kodu czasowego jako fantazyjne-schmancy .NET 3.0 lambda zamiast (nie daj Boże) wycinanie i wklejanie go kilka razy i zastępując DoStuff(s)zDoSomethingElse(s) ?

Wiem, że można to zrobić jako, Delegateale zastanawiam się nad sposobem lambdy.

Odpowiedzi:


129

Co powiesz na rozszerzenie klasy 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;
    }
}

Następnie nazwij to tak:

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

Możesz dodać kolejne przeciążenie, które pomija parametr „iteracje” i wywołuje tę wersję z pewną wartością domyślną (np. 1000).


3
Możesz zamienić sw.Start () na sw.StartNew (), aby zapobiec przypadkowemu zwiększaniu czasu, który upłynął dla każdego kolejnego wywołania s.Time (), przy ponownym użyciu tej samej instancji Stopwatch.
VVS

11
@Jay Zgadzam się, że „foreach” z Enumerable.Range wygląda trochę bardziej „nowocześnie”, ale moje testy pokazują, że jest około cztery razy wolniejsze niż pętla „for” na dużej liczbie. YMMV.
Matt Hamilton,

2
-1: Używanie tutaj rozszerzenia klasy nie ma sensu. Timezachowuje się jak metoda statyczna, odrzucając cały istniejący stan w sw, więc wprowadzenie jej jako metody instancji wygląda po prostu sztucznie.
ildjarn

2
@ildjam Doceniam, że zostawiłeś komentarz wyjaśniający Twój głos negatywny, ale myślę, że źle rozumiesz ideę metod rozszerzających.
Matt Hamilton

4
@Matt Hamilton: Nie sądzę - służą one do dodawania (logicznie) metod instancji do istniejących klas. Ale to nie jest bardziej metoda instancji niż Stopwatch.StartNew, która z jakiegoś powodu jest statyczna. C # nie ma możliwości dodawania statycznych metod do istniejących klas (w przeciwieństwie do F #), więc rozumiem impuls, aby to zrobić, ale nadal pozostawia to zły smak w moich ustach.
ildjarn

33

Oto, czego używałem:

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

Stosowanie:

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

To najlepsze rozwiązanie, jakie kiedykolwiek widziałem! Brak rozszerzenia (dzięki czemu można go używać na wielu klasach) i bardzo czysty!
Calvin,

Nie jestem pewien, czy poprawnie otrzymałem przykład użycia. Kiedy próbuję użyć niektórych Console.WriteLine("")do testowania pod // do stuff that I want to measure, kompilator wcale nie jest zadowolony. Czy masz tam robić normalne wyrażenia i instrukcje?
Tim

@Tim - Jestem pewien, że udało Ci się to rozwiązać, ale w instrukcji using brakowało nawiasu
Alex

12

Możesz spróbować napisać metodę rozszerzającą dla dowolnej używanej klasy (lub dowolnej klasy bazowej).

Wyglądałoby to tak:

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

Następnie metoda rozszerzenia:

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

Każdy obiekt pochodzący z DependencyObject może teraz wywołać TimedFor (..). Funkcję można łatwo dostosować, aby zwracała wartości za pomocą parametrów odniesienia.

-

Jeśli nie chcesz, aby funkcja była powiązana z jakąkolwiek klasą / obiektem, możesz zrobić coś takiego:

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

Wtedy możesz go użyć tak:

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

W przeciwnym razie ta odpowiedź wygląda na przyzwoitą „ogólną” zdolność:

Zawijanie czasu StopWatch z delegatem lub lambdą?


fajnie, ale nie obchodzi mnie sposób, w jaki jest to powiązane z określoną klasą lub klasą bazową; czy można to zrobić bardziej ogólnie?
Jeff Atwood,

Podobnie jak w klasie MyObject, dla której została napisana metoda rozszerzenia? Można go łatwo zmienić, aby rozszerzyć klasę Object lub inną klasę w drzewie dziedziczenia.
Mark Ingram,

Myślałem bardziej statycznie, jak nie przywiązanie do ŻADNEGO konkretnego obiektu lub klasy ... czas i czas są w pewnym sensie uniwersalne
Jeff Atwood,

Doskonale, druga wersja jest bardziej tym, o czym myślałem, +1, ale daję akceptację Mattowi, gdy dotarł do niej pierwszy.
Jeff Atwood,

7

StopWatchKlasa nie musi być Disposedalbo Stoppedw przypadku błędu. Tak, to najprostszy kod do czasu niektóre działania jest

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

Przykładowy kod wywołujący

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

Nie podoba mi się pomysł włączenia iteracji do StopWatchkodu. Zawsze możesz utworzyć inną metodę lub rozszerzenie, które obsługuje wykonywanie Niteracji.

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

Przykładowy kod wywołujący

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

Oto wersje metod rozszerzenia

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

I przykładowy kod wywołujący

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

Przetestowałem metody statyczne i metody rozszerzające (łącząc iteracje i benchmark), a delta oczekiwanego czasu wykonania i rzeczywistego czasu wykonania wynosi <= 1 ms.


Wersje z metodą rozszerzania sprawiają, że ślinka mi do ust. :)
bzlm

7

Jakiś czas temu napisałem prostą klasę CodeProfiler, która opakowała Stopwatch, aby łatwo profilować metodę za pomocą Action: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Pozwoli to również łatwo profilować kod wielowątkowy. Poniższy przykład przedstawi profil lambda akcji z 1-16 wątkami:

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

Zakładając, że potrzebujesz tylko szybkiego pomiaru czasu, jest to łatwe w użyciu.

  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

Możesz przeciążać wiele metod, aby objąć różne przypadki parametrów, które możesz chcieć przekazać do 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;
}

Alternatywnie możesz użyć delegata Func, jeśli musi zwrócić wartość. Możesz również przekazać tablicę (lub więcej) parametrów, jeśli każda iteracja musi używać unikatowej wartości.


2

Dla mnie rozszerzenie wydaje się nieco bardziej intuicyjne w int, nie musisz już tworzyć instancji Stopwatch ani martwić się o jego zresetowanie.

Więc masz:

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

Przy przykładowym użyciu:

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

Przykładowe dane wyjściowe:

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)

1

Lubię używać klas CodeTimer od Vance'a Morrisona (jednego z kolesi od wydajności z .NET).

Na swoim blogu zamieścił post zatytułowany „ Szybki i łatwy pomiar kodu zarządzanego: CodeTimers ”.

Zawiera fajne rzeczy, takie jak MultiSampleCodeTimer. Automatycznie oblicza średnią i odchylenie standardowe, a także bardzo łatwo drukuje wyniki.


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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.