用委托或lambda包装秒表计时?


95

我正在写这样的代码,做一些快速而肮脏的计时:

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

肯定有一个方法来调用此位的计时码作为花式schmancy .NET 3.0的λ,而不是(上帝保佑)剪切和粘贴了几次和更换DoStuff(s)DoSomethingElse(s)

我知道可以做到这一点,Delegate但我想知道lambda方式。

Answers:


129

如何扩展码表类?

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

然后这样称呼它:

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

您可以添加另一个重载,该重载忽略“ iterations”参数,并使用一些默认值(例如1000)调用此版本。


3
您可能希望将sw.Start()替换为sw.StartNew(),以防止意外地增加s.Time()的每个连续调用的经过时间,从而重新使用同一Stopwatch实例。
VVS

11
@Jay我同意Enumerable.Range的“ foreach”看起来更“现代”,但是我的测试表明,在很多情况下,它比“ for”循环慢大约四倍。YMMV。
马特·汉密尔顿,

2
-1:在这里使用类扩展没有意义。Time表现为静态方法,丢弃中的所有现有状态sw,因此将其作为实例方法引入看起来很花哨。
ildjarn 2011年

2
@ildjam我很感谢您发表评论解释您的不赞成投票,但我认为您误解了扩展方法背后的想法。
马特·汉密尔顿

4
@Matt Hamilton:我不这么认为-它们是用于在现有类中添加(逻辑上)实例方法。但是,与相比Stopwatch.StartNew,这只是一个实例方法,由于某种原因它是静态的。C#缺乏将静态方法添加到现有类中的能力(与F#不同),因此我理解这样做的冲动,但仍然给我留下了不好的印象。
ildjarn 2011年

31

这是我一直在使用的:

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

用法:

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

这是我见过的最好的解决方案!没有扩展名(以便可以在许多类上使用)并且非常干净!
加尔文,2016年

我不确定我是否正确使用示例。当我尝试使用Console.WriteLine("")进行测试时// do stuff that I want to measure,编译器一点都不高兴。你应该在那里做正则表达式和陈述吗?
蒂姆(Tim)

@Tim-我确定您已经解决了问题,但是using语句缺少括号
亚历克斯

12

您可以尝试为所使用的任何类(或任何基类)编写扩展方法。

我的电话看起来像:

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

然后是扩展方法:

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

现在,任何从DependencyObject派生的对象都可以调用TimedFor(..)。可以轻松调整该函数以通过ref参数提供返回值。

-

如果您不希望将功能绑定到任何类/对象,则可以执行以下操作:

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

然后您可以像这样使用它:

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

失败的话,这个答案似乎具有某种不错的“通用”能力:

用委托或lambda包装秒表计时?


很酷,但我不在乎它与特定类或基类的联系方式;可以更一般地完成吗?
杰夫·阿特伍德

就像在MyObject类中为扩展方法编写的那样?可以很容易地更改它,以扩展继承树中的Object类或其他类。
马克·英格拉姆

我当时想的是更静态的,就像不绑定任何特定的对象或类。.时间和时间是普遍的
Jeff Atwood,

太好了,第二版比我想的要多+1,但是我首先接受了Matt,因为他第一次接触它。
杰夫·阿特伍德

7

StopWatch类并不需要是DisposedStopped出错。因此,计时某些动作的最简单代码是

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

样本调用代码

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

我不喜欢将迭代包含到StopWatch代码中的想法。您始终可以创建另一个处理执行N迭代的方法或扩展。

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

样本调用代码

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

这是扩展方法版本

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

和示例调用代码

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

我测试了静态方法和扩展方法(将迭代和基准测试相结合),并且预期执行时间与实际执行时间的差异小于等于1毫秒。


扩展方法版本使我垂涎三尺。:)
bzlm

7

前段时间,我编写了一个简单的CodeProfiler类,该类包装了Stopwatch以便使用Action轻松配置方法:http : //www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

它还可以轻松地让您分析多线程代码。以下示例将使用1-16个线程分析动作lambda:

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

假设您只需要快速掌握一件事,这很容易使用。

  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

您可以重载许多方法,以涵盖您可能希望传递给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;
}

另外,如果必须返回一个值,则可以使用Func委托。如果每次迭代都必须使用唯一值,则还可以传入一个(或多个)参数数组。


2

对我而言,扩展在int上感觉更直观,您不再需要实例化秒表或担心将其重置。

所以你有了:

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

带有以下示例用法:

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

样本输出:

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)


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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.