【C#】処理の経過時間を正確に計測する

目次

概要

プログラムの実行速度や、特定の処理にかかった時間を計測するために System.Diagnostics.Stopwatch クラスを使用します。 DateTime の差分を使用するよりも高精度であり、一時停止(Stop)や再開(Start)による時間の積算も簡単に行えます。

仕様(入出力)

  • 入力
    • 計測開始、一時停止、再開のトリガー
  • 出力
    • 経過時間(TimeSpan
    • 実行中かどうかの状態(bool
  • 機能
    • OSの高分解能パフォーマンスカウンターを利用した高精度な計測。
    • 停止後に再度開始することで、経過時間を累積して計測可能。

基本の使い方

インスタンスを作成してスタートし、ストップした時点での Elapsed プロパティを参照します。

using System.Diagnostics;

// 計測開始
var sw = Stopwatch.StartNew();

// ... 何らかの処理 ...

// 計測停止
sw.Stop();

// 結果の表示
Console.WriteLine($"経過時間: {sw.Elapsed}");

コード全文

処理を一時停止し、少し時間を置いてから再開することで、Stopwatchが「計測中の時間のみ」を積算していることを確認するコンソールアプリケーションです。

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("--- 計測を開始します ---");

        // 1. インスタンス生成と計測開始を同時に行う
        Stopwatch sw = Stopwatch.StartNew();

        Console.WriteLine($"[State] IsRunning: {sw.IsRunning}");

        // 処理A(計測対象:1秒)
        Console.WriteLine("処理Aを実行中 (1000ms)...");
        Thread.Sleep(1000);

        // 2. 一時停止
        sw.Stop();
        Console.WriteLine($"[State] IsRunning: {sw.IsRunning}");
        
        // 現時点での経過時間を表示
        // "G" 書式指定子は、一般的な時間形式(d.hh:mm:ss.fffffff)で出力
        Console.WriteLine($"中間タイム: {sw.Elapsed:G}");

        Console.WriteLine("\n--- 計測対象外の時間 (待機中: 2000ms) ---\n");
        Thread.Sleep(2000); // この時間は計測に含まれない

        // 3. 計測再開 (Startメソッドは積算となる)
        Console.WriteLine("--- 計測を再開します ---");
        sw.Start();
        Console.WriteLine($"[State] IsRunning: {sw.IsRunning}");

        // 処理B(計測対象:1.5秒)
        Console.WriteLine("処理Bを実行中 (1500ms)...");
        Thread.Sleep(1500);

        // 4. 最終停止
        sw.Stop();
        Console.WriteLine($"[State] IsRunning: {sw.IsRunning}");

        // 最終的な経過時間を取得
        TimeSpan totalResult = sw.Elapsed;

        Console.WriteLine($"\n最終結果: {totalResult:G}");
        Console.WriteLine($"ミリ秒換算: {sw.ElapsedMilliseconds} ms");
    }
}

実行結果例

--- 計測を開始します ---
[State] IsRunning: True
処理Aを実行中 (1000ms)...
[State] IsRunning: False
中間タイム: 0:00:00:01.0123456

--- 計測対象外の時間 (待機中: 2000ms) ---

--- 計測を再開します ---
[State] IsRunning: True
処理Bを実行中 (1500ms)...
[State] IsRunning: False

最終結果: 0:00:00:02.5234567
ミリ秒換算: 2523 ms

※ 実行環境によりミリ秒以下の誤差が発生します。

カスタムポイント

  • リセットして再利用
    • sw.Reset() を呼ぶと計測時間がゼロに戻り、停止状態になります。同じインスタンスを使い回して別の計測を行う場合に使用します。
    • sw.Restart() はゼロリセットと同時に再スタートを行います。
  • 表示形式の変更
    • sw.Elapsed.ToString(@"hh\:mm\:ss") のように、TimeSpan のカスタム書式指定を使って必要な桁数だけを表示できます。
  • 簡易計測プロパティ
    • 単に「何ミリ秒かかったか」だけを知りたい場合は、TimeSpan を経由せず sw.ElapsedMilliseconds プロパティ(long型)を使用するのが手軽です。

注意点

  1. DateTimeとの違い
    • DateTime.Now の差分で計算すると、OSのシステム時刻変更の影響を受けたり、精度が粗かったりします。処理時間の計測には必ず Stopwatch を使用してください。
  2. IsRunningの確認
    • Start() は計測中であっても例外を出しませんが、Stop() も停止中に呼んでも問題ありません。ただし、論理的に状態を把握したい場合は IsRunning プロパティを確認してください。
  3. 微細なオーバーヘッド
    • Stopwatch クラス自体の操作にもごくわずかなコストがかかります。ナノ秒単位の厳密な計測が必要なループ内などでは留意する必要があります。

応用

処理ブロックの計測クラス

IDisposable を利用して、using ブロックを抜けた瞬間に自動的に時間を計測・出力するヘルパークラスです。

using System;
using System.Diagnostics;

public class MeasureScope : IDisposable
{
    private readonly Stopwatch _sw;
    private readonly string _name;

    public MeasureScope(string name)
    {
        _name = name;
        _sw = Stopwatch.StartNew();
    }

    public void Dispose()
    {
        _sw.Stop();
        Console.WriteLine($"[{_name}] 完了: {_sw.ElapsedMilliseconds} ms");
    }
}

// 使用例
// using (new MeasureScope("重いDB処理"))
// {
//     // 計測したい処理
// }

まとめ

Stopwatch クラスは、アプリケーションのパフォーマンスチューニングやログ記録において最も基本かつ重要なツールです。StartStop を繰り返すことで、特定の処理時間だけを積み上げて計測できるのが最大の特徴です。単純な DateTime の引き算ではなく、このクラスを利用することで、システム時刻の変更に影響されない高精度なベンチマークが可能になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次