StopWatchのタイミングをデリゲートまたはラムダでラップしていますか?


95

私はこのようなコードを書いていて、少し速くて汚いタイミングをとっています:

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

確かに、タイミングコードのこのビットを(神は禁じる)数回カットアンドペーストしてに置き換えるのDoStuff(s)ではなく、派手な.NET 3.0ラムダとして呼び出す方法がありDoSomethingElse(s)ます。

私はそれができることを知っていますがDelegate、ラムダの方法について疑問に思っています。

回答:


129

ストップウォッチクラスを拡張してみませんか?

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

「iterations」パラメーターを省略して、このバージョンをデフォルト値(1000など)で呼び出す別のオーバーロードを追加できます。


3
同じStopwatchインスタンスを再利用して、s.Time()の連続する呼び出しごとに経過時間を誤って増加させないように、sw.Start()をsw.StartNew()に置き換えることができます。
VVS

11
@Jay私はEnumerable.Rangeの "foreach"が少し "モダン"に見えることに同意しますが、私のテストでは、それが大量の "for"ループよりも約4倍遅いことを示しています。YMMV。
マットハミルトン

2
-1:ここでクラス拡張を使用しても意味がありません。Timeは静的メソッドとして動作し、の既存の状態をすべて破棄するswので、インスタンスメソッドとして導入するのは見苦しいだけです。
ildjarn 2011年

2
@ildjam反対票を説明するコメントを残していただきありがとうございますが、拡張メソッドの背後にある考えを誤解していると思います。
Matt Hamilton

4
@マットハミルトン:私はそうは思いません-それらは既存のクラスに(論理的に)インスタンスメソッドを追加するためのものです。ただし、これはインスタンスメソッドではなくStopwatch.StartNew、理由により静的です。C#には、静的メソッドを(F#とは異なり)既存のクラスに追加する機能がないため、これを行う衝動を理解していますが、それでも口の中で味が悪いままです。
ildjarn

31

これが私が使っているものです:

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

@Tim-きっとうまくいきましたが、usingステートメントにブラケットがありませんでした
Alex

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(..)を呼び出すことができます。関数は、ref paramsを介して戻り値を提供するように簡単に調整できます。

-

機能をクラスやオブジェクトに関連付けたくない場合は、次のようにします。

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クラスまたは他のクラスを拡張するように簡単に変更できます。
マークイングラム、

いずれかの特定のオブジェクトまたはクラス..時間やタイミングに関連付けられていないように私は、ソート普遍のものであり、より多くの静的を考えていた
ジェフ・アトウッド

すばらしい、2番目のバージョンは私が考えていたものよりも多く、+ 1ですが、最初にマットに到達したとき、私はマットに受け入れました。
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

先ほど、Stopwatchをラップしてアクションを使用してメソッドを簡単にプロファイリングする簡単なCodeProfilerクラスを作成しました。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

あなたが1つのことの迅速なタイミングが必要であると仮定すると、これは使いやすいです。

  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)


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.