Gói thời gian StopWatch với một đại biểu hoặc lambda?


95

Tôi đang viết mã như thế này, thực hiện một chút thời gian nhanh và bẩn:

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

Chắc chắn có một cách nào đó để gọi đoạn mã thời gian này là một lambda ưa thích-schmancy .NET 3.0 chứ không phải (Chúa cấm) cắt và dán nó một vài lần và thay thế DoStuff(s)bằng DoSomethingElse(s)?

Tôi biết nó có thể được thực hiện như một Delegatenhưng tôi đang băn khoăn về cách lambda.

Câu trả lời:


129

Làm thế nào về việc mở rộng lớp học Đồng hồ bấm giờ?

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

Sau đó gọi nó như thế này:

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

Bạn có thể thêm một quá tải khác bỏ qua tham số "lần lặp" và gọi phiên bản này với một số giá trị mặc định (như 1000).


3
Bạn có thể muốn thay thế sw.Start () bằng sw.StartNew () để ngăn việc vô tình tăng thời gian đã trôi qua cho mọi cuộc gọi liên tiếp của s.Time (), sử dụng lại cùng một phiên bản Đồng hồ bấm giờ.
VVS

11
@Jay Tôi đồng ý rằng "foreach" với Enumerable.Range trông "hiện đại" hơn một chút, nhưng các thử nghiệm của tôi cho thấy rằng nó chậm hơn khoảng bốn lần so với vòng lặp "for" trên một số lượng lớn. YMMV.
Matt Hamilton

2
-1: Sử dụng phần mở rộng lớp ở đây không có ý nghĩa gì. Timehoạt động như một phương thức tĩnh, loại bỏ tất cả trạng thái hiện có trong đó sw, vì vậy việc giới thiệu nó như một phương thức thể hiện trông có vẻ phô trương.
ildjarn

2
@ildjam Tôi đánh giá cao việc bạn đã để lại nhận xét giải thích cho phiếu phản đối của mình, nhưng tôi nghĩ bạn đang hiểu sai ý tưởng đằng sau các phương pháp mở rộng.
Matt Hamilton

4
@Matt Hamilton: Tôi không nghĩ vậy - chúng để thêm (một cách hợp lý) các phương thức cá thể vào các lớp hiện có. Tuy nhiên, đây không phải là một phương thức thể hiện hơn là phương thức Stopwatch.StartNewtĩnh vì một lý do. C # thiếu khả năng thêm các phương thức tĩnh vào các lớp hiện có (không giống như F #), vì vậy tôi hiểu xung lực để làm điều này, nhưng nó vẫn để lại một hương vị tồi tệ trong miệng tôi.
ildjarn

31

Đây là những gì tôi đã sử dụng:

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

Sử dụng:

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

Đây là giải pháp tốt nhất mà tôi từng thấy! Không có tiện ích mở rộng (để nó có thể được sử dụng trên nhiều lớp) và rất sạch sẽ!
Calvin

Tôi không chắc liệu mình có lấy đúng ví dụ sử dụng hay không. Khi tôi cố gắng sử dụng một số Console.WriteLine("")để thử nghiệm // do stuff that I want to measure, thì trình biên dịch không hài lòng chút nào. Bạn có phải thực hiện các biểu thức và câu lệnh bình thường ở đó không?
Tim

@ Tim - Tôi chắc chắn rằng bạn làm việc nó ra, nhưng báo cáo kết quả sử dụng đã có một khung mất tích
Alex

12

Bạn có thể thử viết một phương thức mở rộng cho bất kỳ lớp nào bạn đang sử dụng (hoặc bất kỳ lớp cơ sở nào).

Tôi sẽ có cuộc gọi giống như sau:

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

Sau đó, phương pháp mở rộng:

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

Bất kỳ đối tượng nào xuất phát từ DependencyObject bây giờ có thể gọi TimedFor (..). Chức năng này có thể dễ dàng được điều chỉnh để cung cấp các giá trị trả về thông qua tham số ref.

-

Nếu bạn không muốn chức năng bị ràng buộc với bất kỳ lớp / đối tượng nào, bạn có thể làm như sau:

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

Sau đó, bạn có thể sử dụng nó như:

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

Nếu không, câu trả lời này có vẻ như nó có một số khả năng "chung chung" khá tốt:

Gói thời gian StopWatch với một đại biểu hoặc lambda?


tuyệt, nhưng tôi không quan tâm đến cách điều này được gắn với một lớp cụ thể hoặc lớp cơ sở; nó có thể được thực hiện chung chung hơn không?
Jeff Atwood

Như trong lớp MyObject mà phương thức mở rộng được viết cho? Nó có thể dễ dàng được thay đổi để mở rộng lớp Đối tượng hoặc lớp khác trong cây kế thừa.
Mark Ingram

Tôi đã suy nghĩ nhiều hơn tĩnh, giống như không gắn với bất kỳ đối tượng cụ thể hoặc lớp .. thời gian và thời gian là loại phổ quát
Jeff Atwood

Tuyệt vời, phiên bản thứ 2 đúng hơn những gì tôi đã nghĩ, +1, nhưng tôi đã chấp nhận cho Matt khi anh ấy tiếp cận nó trước.
Jeff Atwood,

7

Các StopWatchlớp không cần phải Disposedhoặc Stoppedvề lỗi. Vì vậy, mã đơn giản nhất để thực hiện một số hành động

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

Mã cuộc gọi mẫu

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

Tôi không thích ý tưởng bao gồm các lần lặp lại vào StopWatchmã. Bạn luôn có thể tạo một phương thức hoặc tiện ích mở rộng khác xử lý các Nlần lặp đang thực thi .

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

Mã cuộc gọi mẫu

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

Đây là các phiên bản phương pháp mở rộng

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

Và mã cuộc gọi mẫu

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

Tôi đã thử nghiệm các phương pháp tĩnh và phương thức mở rộng (kết hợp các lần lặp lại và điểm chuẩn) và thời gian thực thi dự kiến ​​và thời gian thực thi thực tế là <= 1 ms.


Các phiên bản phương pháp mở rộng làm cho miệng của tôi chảy nước. :)
bzlm

7

Tôi đã viết một lớp CodeProfiler đơn giản cách đây một thời gian bao bọc Đồng hồ bấm giờ để dễ dàng cấu hình một phương thức bằng Action: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Nó cũng sẽ dễ dàng cho phép bạn lập hồ sơ mã đa luồng. Ví dụ sau sẽ mô tả hành động lambda với 1-16 chủ đề:

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

Giả sử bạn chỉ cần căn thời gian nhanh chóng một điều này rất dễ sử dụng.

  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

Bạn có thể nạp chồng một số phương thức để bao gồm các trường hợp tham số khác nhau mà bạn có thể muốn truyền cho 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;
}

Ngoài ra, bạn có thể sử dụng đại biểu Func nếu chúng phải trả về một giá trị. Bạn cũng có thể chuyển vào một mảng (hoặc nhiều) tham số nếu mỗi lần lặp phải sử dụng một giá trị duy nhất.


2

Đối với tôi, tiện ích mở rộng cảm thấy trực quan hơn một chút trên int, bạn không còn cần phải khởi tạo Đồng hồ bấm giờ hoặc lo lắng về việc đặt lại nó.

Vì vậy, bạn có:

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

Với cách sử dụng mẫu của:

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

Đầu ra mẫu:

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)

1

Tôi thích sử dụng các lớp CodeTimer từ Vance Morrison (một trong những chàng trai hiệu suất từ ​​.NET).

Anh ấy đã thực hiện một bài đăng trên blog của mình với tiêu đề " Đo lường mã được quản lý một cách nhanh chóng và dễ dàng: CodeTimers ".

Nó bao gồm những thứ hay ho chẳng hạn như MultiSampleCodeTimer. Nó thực hiện tính toán tự động giá trị trung bình và độ lệch chuẩn và cũng rất dễ dàng để in ra kết quả của bạn.


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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.