2008-10-24 9 views
93

Piszę kodu takiego, robi trochę szybki i brudny Czas:Owijanie timera StopWatch przez delegata lub lambda?

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

pewnością istnieje sposób, aby wywołać ten kawałek kodu czasowego jako fantazyjne-SCHMANCY .NET 3.0 lambda zamiast (broń Boże) wycinanie i wklejanie go kilka razy i zastępowanie z z ?

Wiem, że można to zrobić jako Delegate, ale zastanawiam się nad sposobem lambda.

Odpowiedz

127

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 nazwać tak:

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

Można dodać kolejne przeciążenie, które pomija parametr „iteracji” i nazywa tę wersję z pewną wartością domyślną (jak 1000).

+3

Możesz chcieć zastąpić sw.Start() przez sw.StartNew(), aby zapobiec przypadkowemu zwiększeniu czasu, jaki upłynął dla każdego kolejnego wywołania s.Time(), ponownego użycia tej samej instancji Stopwatch. – VVS

+1

Możesz zdobyć jeszcze więcej C# 3.0, i zamienić tę starą instrukcję "for" na foreach (var i in Enumerable.Range (0, iterations)) –

+11

@Jay Zgadzam się, że "foreach" z Enumerable.Range wygląda trochę bardziej "nowoczesne", ale moje testy pokazują, że jest około czterokrotnie wolniejszy niż pętla "dla" w dużej liczbie. YMMV. –

12

Możesz spróbować napisać metodę rozszerzenia dla dowolnej klasy, której używasz (lub dowolnej klasy bazowej).

chciałbym mieć wygląd wywołania takiego:

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

wówczas metodę 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 przedmiot pochodzący z DependencyObject mogą teraz zadzwonić TimedFor (..). Funkcję można łatwo dostosować, aby zapewnić wartości powrotne za pomocą par parametrów.

-

Jeśli nie chcesz funkcjonalność być przywiązane do każdej klasy/obiekt mógłby zrobić coś jak:

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

Następnie można używać go jak:

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

Jeśli to się nie powiedzie, ta odpowiedź wygląda tak, jakby miała jakąś przyzwoitą "ogólną" możliwość:

Wrapping StopWatch timing with a delegate or lambda?

+0

fajne, ale nie obchodzi mnie sposób, w jaki jest to powiązane z konkretną klasą lub klasą bazową; czy można to zrobić bardziej ogólnie? –

+0

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

+0

Myślałem bardziej statycznie, jak nie przywiązałem się do JAKIEGOKOLWIEK konkretnego obiektu lub klasy .. czas i czas są w pewnym sensie uniwersalne –

1

Można przeciążać szereg metod obejmować różne przypadki parametrów może chcesz przejść 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żna użyć delegata Func jeśli muszą zwrócić wartość. Możesz także przekazać tablicę (lub więcej) parametrów, jeśli każda iteracja musi używać unikalnej wartości.

7

napisałem prostą klasę CodeProfiler jakiś czas temu, że owinięty Stoper łatwo profil metodę z użyciem Działanie: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Będzie też łatwo pozwalają profilowania wielowątkowych kodu.Poniższy przykład lambda profil działania z 1-16 nici:

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

I jak korzystać z klas CodeTimer Vance Morrisona (jeden z cegieł wykonania z NET).

Napisał na swoim blogu pod tytułem "Measuring managed code quickly and easiliy: CodeTimers".

Zawiera fajne rzeczy, takie jak MultiSampleCodeTimer. Wykonuje automatyczne obliczanie średniej i odchylenia standardowego, a także bardzo łatwo drukuje wyniki.

4

Zakładając, że potrzebujesz tylko szybkiego pomiaru jednej rzeczy, 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

Dla mnie przedłużenie czuje się trochę bardziej intuicyjny na int, które nie są już potrzebne do wystąpienia stoper lub martwić się o jego anulowanie.

Więc trzeba:

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

z wykorzystaniem próbki:

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

próbki 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) 
26

Oto co Używam:

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

Zastosowanie:

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

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

+0

Nie jestem pewien, czy otrzymałem prawidłowy przykład użycia. Kiedy próbuję użyć 'Console.WriteLine (" ")' do testowania w '// rzeczy, które chcę zmierzyć', to kompilator wcale nie jest szczęśliwy. Czy powinieneś tam robić normalne wyrażenia i stwierdzenia? – Tim

+0

@Tim - Jestem pewien, że udało ci się to, ale instrukcja użycia miała brakujący nawias. – Alex

6

Klasa StopWatch nie musi być Disposed lub Stopped w przypadku błędu. Tak, to najprostszy kod do czasie niektóre działanie jest

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

Przykładowe wywołanie kodu

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

nie podoba mi się pomysł z tym iteracje w kodzie StopWatch. Zawsze można utworzyć inną metodę lub rozszerzenie, które obsługuje wykonywanie iteracji N.

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

kod wywołujący Próbka

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 próbki kodu wywołanie

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

testowałem statyczne metody i sposoby rozszerzenia (grzebień iteracje i benchmark), a delta oczekiwanego czasu wykonania i rzeczywistego czasu wykonania to < = 1 ms.

+0

Wersje metody rozszerzenia sprawiają, że moje usta stają się wodą. :) – bzlm