Bir temsilci veya lambda ile StopWatch zamanlamasını sarma?


96

Bunun gibi kod yazıyorum, biraz hızlı ve kirli zamanlama yapıyorum:

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

Şüphesiz kesme ve bunu bir kaç kez yapıştırarak ve değiştirme (Allah korusun) yerine bir fantezi-schmancy .NET 3.0 lambda olarak zamanlama bu kod bit aramak için bir yol var DoStuff(s)olan DoSomethingElse(s)?

Bunun yapılabileceğini biliyorum Delegateama lambda yolunu merak ediyorum.

Yanıtlar:


129

Kronometre sınıfını genişletmeye ne dersiniz?

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

Öyleyse şöyle deyin:

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

"Yinelemeler" parametresini atlayan ve bu sürümü bazı varsayılan değerlerle (1000 gibi) çağıran başka bir aşırı yük ekleyebilirsiniz.


3
Her ardışık s.Time () çağrısı için geçen sürenin kazara artmasını önlemek ve aynı Kronometre örneğini yeniden kullanmak için sw.Start () öğesini sw.StartNew () ile değiştirmek isteyebilirsiniz.
VVS

11
@Jay Enumerable.Range ile "foreach" in biraz daha "modern" göründüğüne katılıyorum, ancak testlerim bunun "for" döngüsünden yaklaşık dört kat daha yavaş olduğunu gösteriyor. YMMV.
Matt Hamilton

2
-1: Burada bir sınıf uzantısı kullanmak mantıklı değil. Timestatik bir yöntem gibi davranır, içindeki tüm mevcut durumu atar sw, bu nedenle onu bir örnek yöntem olarak tanıtmak sadece hileli görünür.
ildjarn

2
@ildjam Olumsuz oyunuzu açıklayan bir yorum bıraktığınız için minnettarım, ancak uzatma yöntemlerinin arkasındaki fikri yanlış anladığınızı düşünüyorum.
Matt Hamilton

4
@Matt Hamilton: Sanmıyorum - var olan sınıflara (mantıksal olarak) örnek yöntemleri eklemek içindir. Ancak bu, Stopwatch.StartNewbir nedenden ötürü statik olan bir örnek yöntem değildir . C #, mevcut sınıflara statik yöntemler ekleme yeteneğinden yoksundur (F #'ın aksine), bu nedenle bunu yapma dürtüsünü anlıyorum, ancak yine de ağzımda kötü bir tat bırakıyor.
ildjarn

33

İşte kullandığım şey:

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

Kullanım:

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

Bu şimdiye kadar gördüğüm en iyi çözüm! Uzantı yok (böylece birçok sınıfta kullanılabilir) ve çok temiz!
Calvin

Kullanım örneğini doğru aldığımdan emin değilim. Bazılarını Console.WriteLine("")altında test etmek için kullanmaya çalıştığımda // do stuff that I want to measure, derleyici hiç memnun değil. Orada normal ifadeler ve ifadeler yapmanız gerekiyor mu?
Tim

@Tim - Eminim çözmüşsünüzdür, ancak using ifadesinde eksik bir parantez vardı
Alex

12

Kullandığınız sınıf (veya herhangi bir temel sınıf) için bir uzantı yöntemi yazmayı deneyebilirsiniz.

Aramanın şöyle görünmesini isterdim:

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

Ardından uzantı yöntemi:

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'ten türetilen herhangi bir nesne artık TimedFor (..) 'u çağırabilir. İşlev, ref parametreleri aracılığıyla dönüş değerleri sağlamak için kolayca ayarlanabilir.

-

İşlevselliğin herhangi bir sınıfa / nesneye bağlanmasını istemediyseniz, aşağıdaki gibi bir şey yapabilirsiniz:

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

O zaman bunu şu şekilde kullanabilirsiniz:

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

Başarısız olsa da, bu yanıt iyi bir "genel" yeteneğe sahip gibi görünüyor:

Bir temsilci veya lambda ile StopWatch zamanlamasını sarma?


havalı, ancak bunun belirli bir sınıfa veya temel sınıfa bağlanma şekli umrumda değil; daha genel bir şekilde yapılabilir mi?
Jeff Atwood

Uzantı yönteminin yazıldığı MyObject sınıfında olduğu gibi? Miras ağacındaki Object sınıfını veya diğer sınıfı genişletmek için kolayca değiştirilebilir.
Mark Ingram

HİÇBİR belirli bir nesneye veya sınıfa bağlı olmamak gibi daha durağan düşünüyordum .. zaman ve zamanlama bir tür evrenseldir
Jeff Atwood

Mükemmel, 2. versiyon daha çok düşündüğüm şey, +1, ama Matt'e ilk geldiğinde kabul ettim.
Jeff Atwood

7

StopWatchSınıf olmak gerekmez Disposedveya Stoppedhata. Bu nedenle, bazı eylemleri zamanlamak için en basit kod ,

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

Örnek arama kodu

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

Yinelemeleri StopWatchkoda dahil etme fikri hoşuma gitmiyor . Her zaman Nyinelemelerin yürütülmesini işleyen başka bir yöntem veya uzantı oluşturabilirsiniz .

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

Örnek arama kodu

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

İşte uzantı yöntemi sürümleri

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

Ve örnek arama kodu

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

Statik yöntemleri ve uzantı yöntemlerini (yinelemeleri ve karşılaştırmayı birleştiren) test ettim ve beklenen yürütme süresi ve gerçek yürütme süresinin deltası <= 1 ms.


Uzatma yöntemi versiyonları ağzımı sulandırıyor. :)
bzlm

7

Bir süre önce, bir Eylemi kullanarak bir yöntemi kolayca profillemek için Kronometre'yi sarmalayan basit bir CodeProfiler sınıfı yazdım: http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Ayrıca, çok iş parçacıklı kodun profilini kolayca oluşturmanıza olanak sağlar. Aşağıdaki örnek, eylem lambda'nın 1-16 iş parçacığı ile profilini oluşturacaktır:

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

Tek bir şeyin hızlı bir zamanlamasına ihtiyacınız olduğunu varsayarsak, bunun kullanımı kolaydır.

  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'ya iletmek isteyebileceğiniz çeşitli parametre durumlarını kapsayan bir dizi yöntemi aşırı yükleyebilirsiniz:

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

Alternatif olarak, bir değer döndürmeleri gerekiyorsa Func temsilcisini kullanabilirsiniz. Her yinelemenin benzersiz bir değer kullanması gerekiyorsa, bir dizi (veya daha fazla) parametre de iletebilirsiniz.


2

Benim için uzantı int'te biraz daha sezgisel geliyor, artık bir Kronometreyi başlatmanıza veya sıfırlama konusunda endişelenmenize gerek yok.

Yani sizde:

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

Örnek kullanım ile:

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

Örnek çıktı:

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;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.