.NET Core(C#): Stopwatchで経過時間測定(ミリ秒, マイクロ秒, ナノ秒)

はじめに

  • 次の環境を使用して動作確認しています。
    ハードウェアCPU: AMD Ryzen 5 3400G, MEM: 16GB, SSD: 130GB
    OSWindows 10(64ビット)
    IDEMicrosoft Visual Studio Community 2019(16.8.5) + C#(8.0)
  • 型が分かりやすいよう変数宣言ではvarを使用していません。実装時はvarを使用することをおすすめします。
  • 完全なソースコードはこちらで公開しています。

経過時間測定のサンプル

Stopwatchを使用した経過時間測定のサンプルを紹介します。

// 経過時間の計測
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Thread.Sleep(3000);
stopWatch.Stop();

// Elapsed(TimeSpan)からの経過時間取得
TimeSpan elapsed = stopWatch.Elapsed;
double eMili = elapsed.TotalMilliseconds;
double eMicr = eMili * 1000;
double eNano = eMili * 1000 * 1000;
Console.WriteLine($"===== from Stopwatch.Elapsed");
Console.WriteLine($"elapsed={elapsed}");    // 00:00:03.0060116
Console.WriteLine($"1) mili  sec={eMili}"); // 3006.0116
Console.WriteLine($"2) micro sec={eMicr}"); // 3006011.5999999996
Console.WriteLine($"3) nano  sec={eNano}"); // 3006011599.9999995

// ElapsedTicks(long)からの経過時間取得
bool hrEnabled = Stopwatch.IsHighResolution;
long freq = Stopwatch.Frequency;
long ticks = stopWatch.ElapsedTicks;
double tMili = (double)ticks * 1000 / freq;
double tMicr = (double)ticks * 1000 * 1000 / freq;
double tNano = (double)ticks * 1000 * 1000 * 1000 / freq;
Console.WriteLine($"===== from Stopwatch.ElapsedTicks");
Console.WriteLine($"high-resolution performance counter={hrEnabled}"); // true
Console.WriteLine($"Frequency={freq:#,0}[Ticks/sec])"); // 10,000,000
Console.WriteLine($"elapsedTicks={ticks}"); // 30060116
Console.WriteLine($"1) mili  sec={tMili}"); // 3006.0116
Console.WriteLine($"2) micro sec={tMicr}"); // 3006011.6
Console.WriteLine($"3) nano  sec={tNano}"); // 3006011600
  • StopwatchクラスのStart(), Stop()メソッドで計測の開始・終了、Elapsedプロパティから経過時間(TimeSpan型)の取得が行えます。
    再計測する場合は、Reset()/Start()またはRestart()を使用できます。
  • ElapsedTicksプロパティ(long型)を使用して経過時間を取得することもできますが、前述のElapsedから取得した方が簡単です。
    両者は多少の誤差がありますが、ほぼ同じ時間を取得できます。
    (上記では誤差が分かる例を記載していますが、私の環境ではほとんどの実行で一致します。)

ElapsedTicksを使った経過時間の算出方法

  • コンピュータでは、監視や時間計測、音楽の再生等のように正確な時刻に基づいた処理が必要になります。これを実現するために、一定時間間隔で処理を実行できる仕組み(「タイマー実行」「タイマー割り込み」と呼ばれる)を持っています。
  • このタイマー実行の頻度が高いほど細かい単位で時間計測が可能になり、ナノ秒等の精密な時間計測が可能となります。
  • タイマー実行の頻度はハードウェアやOS等の実行環境によって変わります。実行環境が「高解像度パフォーマンスカウンタ(high-resolution performance counter)」に対応している場合は、タイマー実行の頻度が向上します。
  • 高解像度パフォーマンスカウンタを使用しているかはStopwatchのIsHighResolutionプロパティ、タイマー実行の頻度はFrequencyプロパティで取得できます。
    (これらのプロパティは実行環境に依存するので静的プロパティで提供されていると思われます。)
  • 経過時間内でのタイマー実行回数がStopwatch.ElapsedTicksプロパティ([Ticks])です。単位時間でのタイマー実行回数はStopwatch.Frequencyプロパティ([Ticks/秒])です。
    これらを使用して、次のように経過時間[秒]を算出できます。

    経過時間[秒] = ElapsedTicks[Ticks] ÷ Frequency[Ticks/秒]
  • なお、高解像度パフォーマンスカウンタの実装は、チップセットに搭載された高精度イベント タイマー(HPET: High Precision Event Timer)と呼ばれるハードウェアタイマーのことらしいです。
    2005年頃から搭載し始めているため、最近のPCではどれでも搭載していると思われます。
  • DateTime型にもTicksプロパティがありますが、前述のElapsedTicksとは考え方が異なります。DateTimeのTicksは、1Ticks=100ナノ秒(固定)ですが、ElapsedTicksは実行環境で異なる値になります。(ElapsedTicksのRemarksより。)