Membungkus waktu StopWatch dengan delegasi atau lambda?

95

Saya menulis kode seperti ini, melakukan pengaturan waktu yang sedikit cepat dan kotor:

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

Tentunya ada cara untuk menyebut sedikit kode waktu ini sebagai lambda .NET 3.0 yang mewah daripada (Tuhan melarang) memotong dan menempelkannya beberapa kali dan menggantinya DoStuff(s)dengan DoSomethingElse(s)?

Saya tahu itu bisa dilakukan sebagai Delegatetapi saya bertanya-tanya tentang cara lambda.

Jeff Atwood
sumber

Jawaban:

129

Bagaimana dengan memperluas kelas 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;
    }
}

Kemudian sebut seperti ini:

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

Anda dapat menambahkan kelebihan beban lain yang menghilangkan parameter "iterasi" dan memanggil versi ini dengan beberapa nilai default (seperti 1000).

Matt Hamilton
sumber
3
Anda mungkin ingin mengganti sw.Start () dengan sw.StartNew () untuk mencegah secara tidak sengaja menambah waktu yang telah berlalu untuk setiap panggilan berturut-turut s.Time (), menggunakan kembali contoh Stopwatch yang sama.
VVS
11
@Jay Saya setuju bahwa "foreach" dengan Enumerable.Range terlihat sedikit lebih "modern", tetapi pengujian saya menunjukkan bahwa ini sekitar empat kali lebih lambat daripada loop "for" dalam jumlah besar. YMMV.
Matt Hamilton
2
-1: Menggunakan ekstensi kelas di sini tidak masuk akal. Timeberperilaku sebagai metode statis, membuang semua status yang ada di sw, jadi memperkenalkannya sebagai metode instance hanya terlihat menarik perhatian.
ildjarn
2
@ildjam Saya menghargai Anda meninggalkan komentar yang menjelaskan suara negatif Anda, tetapi menurut saya Anda salah memahami gagasan di balik metode penyuluhan.
Matt Hamilton
4
@ Matt Hamilton: Saya tidak berpikir begitu - mereka untuk menambahkan (secara logis) metode instance ke kelas yang ada. Tapi, ini tidak lebih merupakan metode contoh daripada Stopwatch.StartNew, yang statis karena suatu alasan. C # tidak memiliki kemampuan untuk menambahkan metode statis ke kelas yang ada (tidak seperti F #), jadi saya memahami dorongan untuk melakukan ini, tetapi masih meninggalkan rasa tidak enak di mulut saya.
ildjarn
32

Inilah yang telah saya gunakan:

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

Pemakaian:

using (new DisposableStopwatch(t => Console.WriteLine("{0} elapsed", t))) {
  // do stuff that I want to measure
}
Mauricio Scheffer
sumber
Ini adalah solusi terbaik yang pernah saya lihat! Tidak ada ekstensi (sehingga dapat digunakan di banyak kelas) dan sangat bersih!
Calvin
Saya tidak yakin apakah saya mendapatkan contoh penggunaan dengan benar. Ketika saya mencoba menggunakan beberapa Console.WriteLine("")untuk pengujian di bawah // do stuff that I want to measure, maka kompiler tidak senang sama sekali. Apakah Anda seharusnya melakukan ekspresi dan pernyataan normal di sana?
Tim
@ Tim - Saya yakin Anda berhasil, tetapi pernyataan penggunaan memiliki tanda kurung yang hilang
Alex
12

Anda dapat mencoba menulis metode ekstensi untuk kelas apa pun yang Anda gunakan (atau kelas dasar apa pun).

Saya ingin panggilan tersebut terlihat seperti:

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

Kemudian metode ekstensi:

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

Objek apa pun yang diturunkan dari DependencyObject sekarang bisa memanggil TimedFor (..). Fungsi ini dapat dengan mudah disesuaikan untuk memberikan nilai kembali melalui parameter ref.

-

Jika Anda tidak ingin fungsionalitasnya terikat ke kelas / objek apa pun, Anda dapat melakukan sesuatu seperti:

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

Kemudian Anda bisa menggunakannya seperti:

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

Jika gagal, jawaban ini sepertinya memiliki kemampuan "umum" yang layak:

Membungkus waktu StopWatch dengan delegasi atau lambda?

Tandai Ingram
sumber
keren, tapi saya tidak peduli dengan cara ini terkait dengan kelas tertentu atau kelas dasar; dapatkah itu dilakukan secara lebih umum?
Jeff Atwood
Seperti di kelas MyObject yang metode ekstensi ditulis untuk? Itu dapat dengan mudah diubah untuk memperluas kelas Object atau kelas lain di pohon warisan.
Tandai Ingram
Saya berpikir lebih statis, seperti tidak terikat pada objek atau kelas APA PUN .. waktu dan waktu bersifat universal
Jeff Atwood
Luar biasa, versi ke-2 lebih dari apa yang saya pikirkan, +1, tetapi saya memberikan diterima kepada Matt karena dia mendapatkannya terlebih dahulu.
Jeff Atwood
7

The StopWatchkelas tidak perlu Disposedatau Stoppedpada kesalahan. Jadi, kode paling sederhana untuk mengatur waktu beberapa tindakan adalah

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

Contoh kode panggilan

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

Saya tidak suka ide memasukkan iterasi ke dalam StopWatchkode. Anda selalu dapat membuat metode atau ekstensi lain yang menangani Npengulangan eksekusi .

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

Contoh kode panggilan

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

Berikut adalah versi metode ekstensi

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

Dan contoh kode panggilan

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

Saya menguji metode statis dan metode ekstensi (menggabungkan iterasi dan benchmark) dan delta waktu eksekusi yang diharapkan dan waktu eksekusi nyata adalah <= 1 ms.

Anthony Mastrean
sumber
Versi metode ekstensi membuat air liur saya. :)
bzlm
7

Saya menulis kelas CodeProfiler sederhana beberapa waktu lalu yang membungkus Stopwatch untuk dengan mudah membuat profil metode menggunakan Action: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Ini juga akan dengan mudah memungkinkan Anda untuk membuat profil kode multithread. Contoh berikut akan membuat profil lambda tindakan dengan 1-16 utas:

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();
}
Mark S. Rasmussen
sumber
4

Dengan asumsi Anda hanya perlu waktu cepat untuk satu hal, ini mudah digunakan.

  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
sumber
2

Anda dapat membebani sejumlah metode untuk mencakup berbagai kasus parameter yang mungkin ingin Anda teruskan ke 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;
}

Alternatifnya, Anda dapat menggunakan delegasi Func jika mereka harus mengembalikan nilai. Anda juga dapat mengirimkan array (atau lebih) parameter jika setiap iterasi harus menggunakan nilai yang unik.

Morten Christiansen
sumber
2

Bagi saya ekstensi terasa sedikit lebih intuitif di int, Anda tidak perlu lagi membuat Stopwatch atau khawatir tentang mengatur ulang.

Jadi kamu punya:

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

Dengan contoh penggunaan:

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

Output sampel:

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
sumber
1

Saya suka menggunakan kelas CodeTimer dari Vance Morrison (salah satu dudes kinerja dari .NET).

Dia membuat postingan di blognya yang berjudul " Mengukur kode yang dikelola dengan cepat dan mudah: CodeTimers ".

Ini mencakup hal-hal keren seperti MultiSampleCodeTimer. Itu melakukan perhitungan otomatis dari mean dan deviasi standar dan juga sangat mudah untuk mencetak hasil Anda.

Davy Landman
sumber
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
sumber