【问题标题】:Wrapping StopWatch timing with a delegate or lambda?用委托或 lambda 包装秒表计时?
【发布时间】:2010-09-18 23:18:53
【问题描述】:

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

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

当然有一种方法可以将这段时间代码称为花哨的 .NET 3.0 lambda,而不是(上帝保佑)剪切和粘贴几次并将 DoStuff(s) 替换为DoSomethingElse(s)?

我知道它可以作为 Delegate 完成,但我想知道 lambda 方式。

【问题讨论】:

    标签: c# .net delegates lambda


    【解决方案1】:

    如何扩展 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;
        }
    }
    

    然后这样称呼它:

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

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

    【讨论】:

    • 您可能希望将 sw.Start() 替换为 sw.StartNew() 以防止意外增加每次连续调用 s.Time() 所经过的时间,从而重复使用相同的 Stopwatch 实例。跨度>
    • @Jay 我同意带有 Enumerable.Range 的“foreach”看起来更“现代”,但我的测试表明它比大量计数的“for”循环慢四倍。 YMMV。
    • -1 :在这里使用类扩展是没有意义的。 Time 表现为静态方法,丢弃了sw 中的所有现有状态,因此将其作为实例方法引入只是看起来很花哨。
    • @ildjam 感谢您留下评论来解释您的反对意见,但我认为您误解了扩展方法背后的想法。
    • @Matt Hamilton:我不这么认为——它们是为了向现有类添加(逻辑上)实例方法。但是,这与Stopwatch.StartNew 相比不再是一个实例方法,因为某种原因它是静态的。 C# 缺乏向现有类添加静态方法的能力(与 F# 不同),所以我理解这样做的冲动,但它仍然在我的嘴里留下不好的味道。
    【解决方案2】:

    这是我一直在使用的:

    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
    }
    

    【讨论】:

    • 这是我见过的最好的解决方案!没有扩展(因此它可以用于许多类)并且非常干净!
    • 我不确定我是否正确理解了使用示例。当我尝试使用一些Console.WriteLine("") 在// do stuff that I want to measure 下进行测试时,编译器一点也不高兴。你应该在那里做正常的表达和陈述吗?
    • @Tim - 我确定你已经解决了,但是 using 语句缺少括号
    【解决方案3】:

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

    我希望电话看起来像:

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

    如果做不到这一点,这个答案看起来有一些不错的“通用”能力:

    Wrapping StopWatch timing with a delegate or lambda?

    【讨论】:

    • 很酷,但我不关心它与特定类或基类的关联方式;可以更通用吗?
    • 就像在 MyObject 类中编写扩展方法一样?可以很容易地更改它以扩展继承树中的 Object 类或其他类。
    • 我在想更多的是静态的,比如不依赖于任何特定的对象或类。时间和时间是通用的
    • 太好了,第二个版本更符合我的想法,+1,但我先接受了 Matt,因为他先得到了它。
    【解决方案4】:

    StopWatch 类在出错时不需要是 Disposed 或 Stopped。因此,time 一些 action 的最简单代码是

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

    我测试了静态方法和扩展方法(结合迭代和基准测试),预期执行时间和实际执行时间的增量为

    【讨论】:

    • 扩展方法版本让我流口水。 :)
    【解决方案5】:

    我前段时间写了一个简单的 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();
    }
    

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        您可以重载许多方法来涵盖您可能希望传递给 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 委托。如果每次迭代都必须使用唯一值,您还可以传入一个(或更多)参数数组。

        【讨论】:

          【解决方案8】:

          对我来说,扩展在 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)
          

          【讨论】:

            【解决方案9】:

            我喜欢使用 Vance Morrison 的 CodeTimer 类(来自 .NET 的性能专家之一)。

            他在自己的博客上发表了一篇题为“Measuring managed code quickly and easiliy: CodeTimers”的帖子。

            它包括很酷的东西,例如 MultiSampleCodeTimer。它会自动计算平均值和标准差,并且很容易打印出你的结果。

            【讨论】:

              【解决方案10】:
              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;
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2010-10-30
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多