コンピューターでさまざまなパラメーターのアルゴリズムをテストしています。パラメータごとにパフォーマンスが変動することに気付きました。
最初に実行すると 20 ミリ秒、2 回目には 5 ミリ秒、3 回目には 4 ミリ秒が得られたとします。しかし、アルゴリズムはこれら 3 回で同じように機能するはずです。
C# ライブラリを使用stopwatch
して時間をカウントしていますが、これらの変動を受けずにパフォーマンスを測定するより良い方法はありますか?
コンピューターでさまざまなパラメーターのアルゴリズムをテストしています。パラメータごとにパフォーマンスが変動することに気付きました。
最初に実行すると 20 ミリ秒、2 回目には 5 ミリ秒、3 回目には 4 ミリ秒が得られたとします。しかし、アルゴリズムはこれら 3 回で同じように機能するはずです。
C# ライブラリを使用stopwatch
して時間をカウントしていますが、これらの変動を受けずにパフォーマンスを測定するより良い方法はありますか?
ベンチマークの世界へようこそ。
4 ミリ秒で終了するコードでは、1 回実行しただけでは正確な測定値を取得できません。ページ フォールト、静的初期化子、JIT、CPU キャッシュ、分岐予測子、およびコンテキスト スイッチはすべて実行時間に影響を与え、その小さなテストでは、それらのいずれかが簡単に大きな差異を生み出す可能性があります。ストップウォッチが 1 秒または 2 秒を超えるまでループで実行し、実行した回数で割ります。
また、コードが JIT され、CPU のキャッシュでホットであることを確認するために、を開始する前にコードを 1 回または 2 回実行する必要があります。Stopwatch
プロファイリングの代わりに貧弱なベンチマークが必要な場合に使用するコードを次に示します。これは、上で説明したことよりも少し多くのことを行います。たとえば、ベンチマークからストップウォッチのオーバーヘッドを取り除き、最小の実行時間を見つけたと確信できるまでドリルダウンします。
runCount = 次に短い実行時間をチェックする回数。
subRunCount = 渡した Action がすでにループを実行している場合、または複数のアイテムで実行されている場合など、アイテムごとに費やされた時間を測定したい場合は、ここにカウントを入れます。
static double Benchmark(string name, int runCount, int subRunCount, Action action)
{
Console.WriteLine("{0}: warming up...", name);
// warm up.
action();
Console.WriteLine("{0}: finding ballpark speed...", name);
// find an average amount of calls it fill up two seconds.
Stopwatch sw = Stopwatch.StartNew();
int count = 0;
do
{
++count;
action();
}
while (sw.ElapsedTicks < (Stopwatch.Frequency * 2));
sw.Stop();
Console.WriteLine("{0}: ballpark speed is {1} runs/sec", name, MulMulDiv(count, subRunCount, Stopwatch.Frequency, sw.ElapsedTicks));
// The benchmark will run the Action in a loop 'count' times.
count = Math.Max(count / 2, 1);
// Start the benchmark.
Console.Write("{0}: benchmarking", name);
Console.Out.Flush();
long minticks = long.MaxValue;
int runs = 0;
while (runs < runCount)
{
sw.Restart();
for (int i = 0; i < count; ++i)
{
action();
}
sw.Stop();
long ticks = sw.ElapsedTicks;
if (ticks < minticks)
{
// Found a new smallest execution time. Reset.
minticks = ticks;
runs = 0;
Console.Write('+');
Console.Out.Flush();
continue;
}
else
{
++runs;
Console.Write('.');
Console.Out.Flush();
}
}
Console.WriteLine("done");
Console.WriteLine("{0}: speed is {1} runs/sec", name, MulMulDiv(count, subRunCount, Stopwatch.Frequency, minticks));
return (double)count * subRunCount * Stopwatch.Frequency / minticks;
}
static long MulMulDiv(long count, long subRunCount, long freq, long ticks)
{
return (long)((BigInteger)count * subRunCount * freq / ticks);
}
実行時にジャストインタイム (JIT) コンパイラが MSIL をネイティブ コードに変換するため、最初の実行には時間がかかります(マネージ実行プロセスを参照)。
最初の実行の問題を解消するには、次のいずれかを使用できます。
Native Image Generator (Ngen.exe) は、マネージ アプリケーションのパフォーマンスを向上させるツールです。Ngen.exe は、コンパイルされたプロセッサ固有のマシン コードを含むファイルであるネイティブ イメージを作成し、ローカル コンピューターのネイティブ イメージ キャッシュにインストールします。ランタイムは、ジャストインタイム (JIT) コンパイラを使用して元のアセンブリをコンパイルする代わりに、キャッシュからのネイティブ イメージを使用できます。
コードを含めずStopWatch
にプロファイリングするには、Visual Studio に含まれているパフォーマンス プロファイラーのインストルメンテーション プロファイリングを使用できます。
Visual Studioのインストルメンテーション プロファイリングメソッドは、プロファイリングされたアプリケーションの関数呼び出し、行、および命令の詳細なタイミング情報を記録します。
「ホット パス」を分析し、複数の異なる実行からのレポートを比較することもできます。