การตัดเวลา StopWatch ด้วยผู้รับมอบสิทธิ์หรือแลมบ์ดา?


97

ฉันกำลังเขียนโค้ดแบบนี้โดยใช้เวลาที่รวดเร็วและสกปรกเล็กน้อย:

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 lambda แทนที่จะตัดและวาง (พระเจ้าห้าม) สองสามครั้งและแทนที่DoStuff(s)ด้วยDoSomethingElse(s)?

ฉันรู้ว่ามันสามารถทำได้Delegateแต่ฉันสงสัยเกี่ยวกับวิธีแลมด้า

คำตอบ:


130

แล้วการขยายคลาสนาฬิกาจับเวลาล่ะ?

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)


3
คุณอาจต้องการแทนที่ sw.Start () ด้วย sw.StartNew () เพื่อป้องกันการเพิ่มเวลาที่ผ่านไปโดยไม่ได้ตั้งใจสำหรับการโทร s.Time ทุกครั้งที่ติดต่อกันโดยใช้อินสแตนซ์นาฬิกาจับเวลาเดิมซ้ำ
VVS

11
@ ฉันยอมรับว่า "foreach" กับ Enumerable.Range ดู "ทันสมัย" กว่าเล็กน้อย แต่การทดสอบของฉันแสดงให้เห็นว่าช้ากว่าการวนซ้ำ "for" ประมาณสี่เท่าในการนับจำนวนมาก YMMV.
Matt Hamilton

2
-1: การใช้ส่วนขยายคลาสที่นี่ไม่สมเหตุสมผล Timeทำงานเป็นวิธีการแบบคงที่โดยทิ้งสถานะที่มีอยู่ทั้งหมดในswดังนั้นการแนะนำเป็นวิธีการอินสแตนซ์จึงดูเป็นลูกเล่น
ildjarn

2
@ildjam ฉันขอขอบคุณที่คุณแสดงความคิดเห็นอธิบายการลงคะแนนของคุณ แต่ฉันคิดว่าคุณเข้าใจผิดแนวคิดเบื้องหลังวิธีการขยาย
Matt Hamilton

4
@Matt Hamilton: ฉันไม่คิดอย่างนั้น - มันมีไว้สำหรับเพิ่มวิธีการอินสแตนซ์ (เชิงเหตุผล) ให้กับคลาสที่มีอยู่ แต่นี่ไม่ใช่วิธีการของอินสแตนซ์มากไปกว่าStopwatch.StartNewซึ่งเป็นแบบคงที่ด้วยเหตุผล C # ขาดความสามารถในการเพิ่มวิธีการคงที่ให้กับคลาสที่มีอยู่ (ไม่เหมือน F #) ดังนั้นฉันจึงเข้าใจถึงแรงกระตุ้นในการทำสิ่งนี้ แต่ก็ยังคงทิ้งรสชาติที่ไม่ดีไว้ในปากของฉัน
ildjarn

34

นี่คือสิ่งที่ฉันใช้:

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
}

นี่คือทางออกที่ดีที่สุดที่ฉันเคยเห็น! ไม่มีส่วนขยาย (เพื่อให้สามารถใช้กับหลายชั้นเรียน) และสะอาดมาก!
Calvin

ฉันไม่แน่ใจว่าฉันมีตัวอย่างการใช้งานที่ถูกต้องหรือไม่ เมื่อฉันพยายามใช้บางส่วนConsole.WriteLine("")สำหรับการทดสอบภายใต้// do stuff that I want to measureคอมไพเลอร์ไม่มีความสุขเลย คุณควรจะแสดงสำนวนและข้อความปกติที่นั่นหรือไม่?
ทิม

@ ทิม - ฉันแน่ใจว่าคุณใช้งานได้ แต่คำสั่งใช้มีวงเล็บที่ขาดหายไป
อเล็กซ์

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 (.. ) ได้แล้ว ฟังก์ชันนี้สามารถปรับเปลี่ยนได้อย่างง่ายดายเพื่อให้ค่าส่งคืนผ่านพารามิเตอร์อ้างอิง

-

หากคุณไม่ต้องการให้ฟังก์ชันการทำงานเชื่อมโยงกับคลาส / อ็อบเจ็กต์ใด ๆ คุณสามารถทำสิ่งต่อไปนี้:

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

หากล้มเหลวคำตอบนี้ดูเหมือนว่ามีความสามารถ "ทั่วไป" ที่ดี:

การตัดเวลา StopWatch ด้วยผู้รับมอบสิทธิ์หรือแลมบ์ดา?


เจ๋ง แต่ฉันไม่สนใจว่าวิธีนี้จะเชื่อมโยงกับคลาสใดคลาสหนึ่งหรือคลาสพื้นฐาน สามารถทำได้มากกว่านี้หรือไม่?
Jeff Atwood

เช่นเดียวกับในคลาส MyObject ที่เมธอดส่วนขยายถูกเขียนขึ้นเพื่อ? สามารถเปลี่ยนเพื่อขยายคลาส Object หรือคลาสอื่น ๆ ในแผนผังการสืบทอดได้อย่างง่ายดาย
Mark Ingram

ฉันคิดว่าคงที่มากขึ้นเช่นไม่ได้ผูกติดกับวัตถุหรือคลาสใด ๆ โดยเฉพาะ .. เวลาและเวลาเป็นเรื่องสากล
Jeff Atwood

ยอดเยี่ยมรุ่นที่ 2 เป็นมากกว่าสิ่งที่ฉันคิด +1 แต่ฉันยอมรับ Matt ในขณะที่เขาไปถึงก่อน
Jeff Atwood

7

StopWatchชั้นไม่จำเป็นต้องมีDisposedหรือStoppedข้อผิดพลาด ดังนั้นรหัสที่ง่ายที่สุดในเวลาบางการกระทำคือ

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:

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

คุณสามารถโอเวอร์โหลดวิธีการได้หลายวิธีเพื่อให้ครอบคลุมกรณีต่างๆของพารามิเตอร์ที่คุณอาจต้องการส่งผ่านไปยังแลมบ์ดา:

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)

1

ฉันชอบใช้คลาส CodeTimer จาก Vance Morrison (หนึ่งในประสิทธิภาพการทำงานจาก. NET)

เขาโพสต์ในบล็อกของเขาชื่อ "การวัดโค้ดที่มีการจัดการอย่างรวดเร็วและง่ายดาย: CodeTimers "

รวมถึงสิ่งที่น่าสนใจเช่น MultiSampleCodeTimer มันคำนวณค่าเฉลี่ยและส่วนเบี่ยงเบนมาตรฐานโดยอัตโนมัติและยังพิมพ์ผลลัพธ์ของคุณได้ง่ายมาก


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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.