35

私のいくつかのメソッドをベンチマークするための簡単なクラスを作成しました。しかし、それは正確ですか?私はベンチマークやタイミングなどに不慣れなので、ここでフィードバックを求めることができると思いました。また、それが良ければ、他の誰かがそれを利用できるかもしれません:)

public static class Benchmark
{
    public static IEnumerable<long> This(Action subject)
    {
        var watch = new Stopwatch();
        while (true)
        {
            watch.Reset();
            watch.Start();
            subject();
            watch.Stop();
            yield return watch.ElapsedTicks;
        }
    }
}

次のように使用できます。

var avg = Benchmark.This(() => SomeMethod()).Take(500).Average();

フィードバックはありますか?それはかなり安定していて正確に見えますか、それとも何か見逃していますか?

4

4 に答える 4

21

これは、単純なベンチマークと同じくらい正確です。ただし、制御できない要因がいくつかあります。

  • 他のプロセスからのシステムへの負荷
  • ベンチマーク前/ベンチマーク中のヒープの状態

最後のポイントについて何かできることがあります。ベンチマークは、コールGC.Collectを防御できるまれな状況の 1 つです。subjectまた、JIT の問題を排除するために、事前に 1 回呼び出すこともできます。しかし、それには呼び出しsubjectが独立している必要があります。

public static IEnumerable<TimeSpan> This(Action subject)
{
    subject();     // warm up
    GC.Collect();  // compact Heap
    GC.WaitForPendingFinalizers(); // and wait for the finalizer queue to empty

    var watch = new Stopwatch();
    while (true)
    {
        watch.Reset();
        watch.Start();
        subject();
        watch.Stop();
        yield return watch.Elapsed;  // TimeSpan
    }
}

System.Diagnostics.Stopwatch.IsHighResolutionおまけとして、クラスはフィールドをチェックする必要があります。オフの場合、非常に粗い (20 ミリ秒) 解像度しかありません。

しかし、通常の PC では、多くのサービスがバックグラウンドで実行されているため、それほど正確になることはありません。

于 2009-10-02T09:35:37.570 に答える
10

ここでカップルの問題。

最初に、コードを初めて実行するときに、そのメソッド呼び出しの推移閉包がジットされることを覚えておいてください。つまり、最初の実行は、後続のすべての実行よりもコストが高くなる可能性があります。「コールド」タイミングと「ホット」タイミングのどちらをベンチマークするかによって、違いが生じる可能性があります。メソッドをジッティングするコストが、他のすべての呼び出しをまとめたものよりも高いメソッドを見てきました。

次に、ガベージ コレクタが別のスレッドで実行されることを思い出してください。1回の実行でガベージを作成している場合、そのガベージをクリーンアップするコストは、次の実行まで実現されない可能性があります。したがって、1 回の実行の総コストを、後の実行に押し付けて説明することに失敗しています。

これらは両方とも、すべてのベンチマークの弱点を示しています。ベンチマークは本質的に非現実的であり、したがって価値が限られています。実際のコードでは、GC が実行され、ジッターが実行されます。ベンチマークでは、大規模システムに固有の実際のコストの変動性が考慮されていないため、ベンチマークされたパフォーマンスが実際のパフォーマンスとはまったく異なる場合がよくあります。パフォーマンス特性を個別に分析するよりも、実際の顧客が実際に直面している現実的なシナリオのパフォーマンス特性を調べることを好みます。

于 2009-10-02T17:07:24.683 に答える
7

ElapsedTicks ではなく、必ず ElapsedMilliseconds を返す必要があります。ElapsedTicks によって返される値は、ストップウォッチの頻度に依存し、システムによって異なる場合があります。Timespan または DateTime オブジェクトの Ticks プロパティに必ずしも対応するとは限りません。

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspxを参照してください。

Ticks の追加の解像度が必要な場合watch.Elapsed.Ticks、代わりに (Timestamp.Ticks)を返す必要watch.ElapsedTicksがあります (これは、.Net で最も微妙な潜在的なエラーの 1 つになる可能性があります)。MSDN から:

ストップウォッチ ティックは、DateTime.Ticks とは異なります。DateTime.Ticks 値の各ティックは、100 ナノ秒間隔を表します。ElapsedTicks 値の各ティックは、1 秒を Frequency で割った時間間隔を表します。

それ以外は、コードは問題ないと思いますが、メソッド呼び出しのオーバーヘッドの一部を測定に含めていると思いますが、メソッド自体の実行にほとんど時間がかからない場合、これは重要になる可能性があります。また、計算された平均からメソッドへの最初の呼び出しを除外することをお勧めしますが、クラスでそれを行う方法がわかりません。

おそらくこのクラスのほとんどの用途には関係ない最後のポイント: ストップウォッチは、システム時間に比べて少し速く実行されます。私のコンピューターでは、24 時間後に約 5 秒 (ミリではなく秒) 進み、他のマシンではこのずれがさらに大きくなる可能性があります。したがって、実際には非常に粒度が高いのに、非常に正確であると言うのは少し誤解を招きます。短期間の方法のタイミングを計る場合、これは明らかに重大な問題にはなりません。

最後にもう 1 つ重要な点があります。ベンチマークを行っているときに、すべてが狭い範囲の値 (80、80、79、82 など) に集中している一連の実行時間が得られることによく気づきました。 、しかし、時折、Windowsで何か他のことが起こり(別のプログラムを開く、またはアンチウイルスが起動するなど)、他のものとはまったく違う値が得られます(たとえば、80、80、79、271、80など) .)。この異常値の問題に対する簡単な解決策は、測定値の平均ではなく中央値を使用することだと思います。Linq がこれを自動的にサポートするかどうかはわかりません。

于 2009-10-02T02:21:50.283 に答える
2

私は C# プログラマーではないので、そのクラスが関数の実行にかかる時間をカウントするための適切な実装であるかどうかを正確に言うことはできません。ただし、再現性と精度に関して留意すべき点があります。

私は .NET Framework のさまざまな詳細について詳しくは知りませんが、ネイティブ コードへのコンパイル方法によっては、コンパイルがベンチマークの結果に影響を与える可能性があります。また、関数がキャッシュにあるかどうかによっても違いが生じます。そのため、関数をループして、コンパイルからのヒットがないこと、およびすべてが読み込まれて準備が整っていることを確認する必要があります。それができたら、始められるかもしれません。

他の人は、おそらく私よりも .NET に関する情報と知識を持っているでしょう。

于 2009-10-02T02:20:35.600 に答える