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.

Servy
  • 202,030
  • 26
  • 332
  • 449
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153

10 Answers10

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

Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 3
    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. – VVS Oct 24 '08 at 14:20
  • 1
    You could get even more C# 3.0, and replace that old-fashed 'for' statement with foreach(var i in Enumerable.Range(0, iterations)) – Jay Bazuzi Oct 24 '08 at 16:46
  • 11
    @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. – Matt Hamilton Oct 25 '08 at 23:16
  • 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. – StriplingWarrior Jun 28 '10 at 18:30
  • 2
    -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. – ildjarn Mar 21 '11 at 13:14
  • 2
    @ildjam I appreciate that you left a comment explaining your downvote, but I think you're misunderstanding the idea behind extension methods. – Matt Hamilton Mar 21 '11 at 19:51
  • 4
    @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. – ildjarn Apr 02 '11 at 08:18
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
}
Alex
  • 2,815
  • 2
  • 24
  • 22
Mauricio Scheffer
  • 98,863
  • 23
  • 192
  • 275
  • This is the best solution I've ever seen! No extension (so that it can be used on many classes) and very clean! – Calvin Feb 03 '16 at 23:00
  • 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? – Tim Dec 08 '17 at 14:27
  • @Tim - I'm sure you worked it out, but the using statement had a missing bracket – Alex Jan 10 '18 at 20:23
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?

Community
  • 1
  • 1
Mark Ingram
  • 71,849
  • 51
  • 176
  • 230
  • 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? – Jeff Atwood Oct 24 '08 at 08:47
  • 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. – Mark Ingram Oct 24 '08 at 08:54
  • I was thinking more static, like not tied to ANY particular object or class.. time and timing is sort of universal – Jeff Atwood Oct 24 '08 at 09:01
  • Excellent, the 2nd version is more what I was thinking, +1, but I give accepted to Matt as he got to it first. – Jeff Atwood Oct 24 '08 at 09:21
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.

Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
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();
}
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
Mark S. Rasmussen
  • 34,696
  • 4
  • 39
  • 58
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; }
    }
}
jyoung
  • 5,071
  • 4
  • 30
  • 47
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.

Morten Christiansen
  • 19,002
  • 22
  • 69
  • 94
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)
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
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.

Davy Landman
  • 15,109
  • 6
  • 49
  • 73
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;
    }
}
Alper Ebicoglu
  • 8,884
  • 1
  • 49
  • 55