Wrapping StopWatch timing with a delegate or lambda?
Asked Answered
P

10

98

I'm writing code like this, doing a little quick and dirty timing:

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

Surely there's a way to call this bit of timing code as a fancy-schmancy .NET 3.0 lambda rather than (God forbid) cutting and pasting it a few times and replacing the DoStuff(s) with DoSomethingElse(s)?

I know it can be done as a Delegate but I'm wondering about the lambda way.

Pneumectomy answered 24/10, 2008 at 8:39 Comment(0)
M
134

How about extending the Stopwatch class?

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

Then call it like this:

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

You could add another overload which omits the "iterations" parameter and calls this version with some default value (like 1000).

Metronymic answered 24/10, 2008 at 8:55 Comment(7)
You might want to replace sw.Start() with sw.StartNew() to prevent accidentially increasing the elapsed time for every consecutive call of s.Time(), reusing the same Stopwatch instance.Marmalade
You could get even more C# 3.0, and replace that old-fashed 'for' statement with foreach(var i in Enumerable.Range(0, iterations))Treasonable
@Jay I agree that "foreach" with Enumerable.Range looks a bit more "modern", but my tests show that it's about four times slower than a "for" loop over a large count. YMMV.Metronymic
I'm not seeing a StartNew() method, but maybe VVS means to replace the sw.Reset(); sw.Start(); with sw.Restart();? Sounds like a matter of preference to me.Poly
-1 : Using a class extension here makes no sense. Time behaves as a static method, discarding all existing state in sw, so introducing it as an instance method just looks gimmicky.Unbiased
@ildjam I appreciate that you left a comment explaining your downvote, but I think you're misunderstanding the idea behind extension methods.Metronymic
@Matt Hamilton : I don't think so -- they're for adding (logically) instance methods to existing classes. But, this is no more an instance method than Stopwatch.StartNew, which is static for a reason. C# lacks the ability to add static methods to existing classes (unlike F#), so I understand the impulse to do this, but it still leaves a bad taste in my mouth.Unbiased
H
37

Here's what I've been using:

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
}
Haber answered 13/5, 2009 at 1:14 Comment(3)
This is the best solution I've ever seen! No extension (so that it can be used on many classes) and very clean!Giese
I'm not sure if I got the usage example correctly. When I try to use some Console.WriteLine("") for testing under // do stuff that I want to measure, then the compiler is not happy at all. Are you supposed to do normal expressions and statements there?Liebig
@Liebig - I'm sure you worked it out, but the using statement had a missing bracketAkerboom
F
12

You could try writing an extension method for whatever class you're using (or any base class).

I would have the call look like:

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

Then the extension method:

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

Any object deriving from DependencyObject can now call TimedFor(..). The function can easily be adjusted to provide return values via ref params.

--

If you didn't want the functionality to be tied to any class / object you could do something like:

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

Then you could use it like:

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

Failing that, this answer looks like it has some decent "generic" ability:

Wrapping StopWatch timing with a delegate or lambda?

Funerary answered 24/10, 2008 at 8:44 Comment(4)
cool, but I don't care for the way this is tied to a particular class or base class; can it be done more generically?Pneumectomy
As in the MyObject class that the extension method is written for? It can easily be changed to extend the Object class or other class in the inheritance tree.Funerary
I was thinking more static, like not tied to ANY particular object or class.. time and timing is sort of universalPneumectomy
Excellent, the 2nd version is more what I was thinking, +1, but I give accepted to Matt as he got to it first.Pneumectomy
C
7

I wrote a simple CodeProfiler class some time ago that wrapped Stopwatch to easily profile a method using an Action: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

It'll also easily allow you to profile the code multithreaded. The following example will profile the action lambda with 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();
}
Caitlin answered 24/10, 2008 at 8:58 Comment(0)
F
7

The StopWatch class does not need to be Disposed or Stopped on error. So, the simplest code to time some action is

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

Sample calling code

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

I don't like the idea of including the iterations into the StopWatch code. You can always create another method or extension that handles executing N iterations.

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

Sample calling code

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

Here are the extension method versions

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

And sample calling code

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

I tested the static methods and extension methods (combining iterations and benchmark) and the delta of expected execution time and real execution time is <= 1 ms.

Fraxinella answered 30/11, 2009 at 16:44 Comment(1)
The extension method versions make my mouth water. :)Kane
I
4

Assuming you just need a quick timing of one thing this is easy to use.

  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; }
    }
}
Involution answered 24/10, 2008 at 13:27 Comment(0)
S
2

You can overload a number of methods to cover various cases of parameters you might want to pass to the 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;
}

Alternatively, you can use the Func delegate if they must return a value. You can also pass in an array (or more) of parameters if each iteration must use a unique value.

Shoot answered 24/10, 2008 at 8:55 Comment(0)
G
2

For me the extension feels a little bit more intuitive on int, you no longer need to instantiate a Stopwatch or worry about resetting it.

So you have:

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

With the sample usage of:

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

Sample output:

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)
Gunny answered 12/4, 2009 at 11:28 Comment(0)
W
1

I like to use the CodeTimer classes from Vance Morrison (one of the performance dudes from .NET).

He made a post on on his blog titled "Measuring managed code quickly and easiliy: CodeTimers".

It includes cool stuff such as a MultiSampleCodeTimer. It does automatic calculation of the mean and standard deviation and its also very easy to print out your results.

Wofford answered 24/10, 2008 at 9:48 Comment(0)
C
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;
    }
}
Cowans answered 8/8, 2019 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.