はじめに 「ストップウォッチ」は“処理時間を見える化する道具”
「この処理、どれくらい時間かかってるんだろう?」
「最適化したつもりだけど、本当に速くなった?」
こういう“処理時間の見える化”に使うのが、C# の System.Diagnostics.Stopwatch です。
「開始」「停止」「経過時間の取得」という、まさにストップウォッチそのものの API を持っています。
ここでは、
まず「一番シンプルな使い方」から入り、
「ラップ(区間計測)」「ユーティリティ化」「ハマりどころ」まで、
初心者向けにかみ砕いて説明します。
基本形:Stopwatch で処理時間を測る
一番シンプルな「開始→処理→停止→経過時間」
まずは「ある処理にどれくらい時間がかかったか」を測る最小パターンです。
using System;
using System.Diagnostics;
public class StopwatchSample
{
public static void Main()
{
var sw = Stopwatch.StartNew(); // StartNew が一番ラク
// 計測したい処理
DoHeavyWork();
sw.Stop();
Console.WriteLine($"経過ミリ秒: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"経過時間: {sw.Elapsed}");
}
private static void DoHeavyWork()
{
// ダミーで少し時間のかかる処理
for (int i = 0; i < 10_000_000; i++)
{
_ = Math.Sqrt(i);
}
}
}
C#ここで押さえてほしいポイントは 3つです。
Stopwatch.StartNew() で「インスタンス生成+計測開始」を一気にやる。Stopwatch.Stop() で計測を止める。Elapsed(TimeSpan)や ElapsedMilliseconds で経過時間を読む。
Elapsed は TimeSpan なので、TotalSeconds や TotalMilliseconds など、好きな単位で取り出せます。
ラップ計測:途中途中の区間時間を測る
「処理A」「処理B」「処理C」それぞれの時間を知りたい
1つのストップウォッチで、処理の区間ごとに時間を測ることもできます。
using System;
using System.Diagnostics;
public class StopwatchLapSample
{
public static void Main()
{
var sw = Stopwatch.StartNew();
DoWorkA();
Console.WriteLine($"A 終了: {sw.ElapsedMilliseconds} ms");
DoWorkB();
Console.WriteLine($"B 終了: {sw.ElapsedMilliseconds} ms");
DoWorkC();
Console.WriteLine($"C 終了: {sw.ElapsedMilliseconds} ms");
sw.Stop();
}
private static void DoWorkA()
{
for (int i = 0; i < 3_000_000; i++) { _ = Math.Sqrt(i); }
}
private static void DoWorkB()
{
for (int i = 0; i < 2_000_000; i++) { _ = Math.Sqrt(i); }
}
private static void DoWorkC()
{
for (int i = 0; i < 1_000_000; i++) { _ = Math.Sqrt(i); }
}
}
C#この書き方だと、
「開始から A 終了まで」「開始から B 終了まで」…の累積時間が分かります。
もし「A だけの時間」「B だけの時間」が欲しければ、Elapsed の値を一時変数に覚えておいて差を取る、というやり方もあります。
var sw = Stopwatch.StartNew();
DoWorkA();
var afterA = sw.Elapsed;
DoWorkB();
var afterB = sw.Elapsed;
TimeSpan aOnly = afterA;
TimeSpan bOnly = afterB - afterA;
C#「累積時間」と「区間時間」をどう使い分けるかを意識すると、
ストップウォッチの使い方が一段クリアになります。
ユーティリティ化:処理を渡すと時間を測ってくれるメソッド
「計測の型」を決めておくと毎回ラクになる
毎回 Stopwatch を書くのが面倒なら、
「処理を渡すと時間を測ってくれる」小さなユーティリティを作っておくと便利です。
using System;
using System.Diagnostics;
public static class MeasureUtil
{
public static TimeSpan Measure(Action action)
{
var sw = Stopwatch.StartNew();
action();
sw.Stop();
return sw.Elapsed;
}
}
C#使い方の例です。
TimeSpan elapsed = MeasureUtil.Measure(() =>
{
DoHeavyWork();
});
Console.WriteLine($"処理時間: {elapsed.TotalMilliseconds} ms");
C#非同期処理版も作れます。
using System.Threading.Tasks;
public static class MeasureUtilAsync
{
public static async Task<TimeSpan> MeasureAsync(Func<Task> action)
{
var sw = Stopwatch.StartNew();
await action();
sw.Stop();
return sw.Elapsed;
}
}
C#TimeSpan elapsed = await MeasureUtilAsync.MeasureAsync(async () =>
{
await Task.Delay(1000);
});
C#こうして「計測の型」を決めておくと、
「この処理、ちょっと時間測ってみよう」が一瞬で書けるようになります。
実務でのハマりどころとコツ
DateTime.Now ではなく Stopwatch を使う理由
「開始時刻を DateTime.Now で取って、終了時刻との差を取ればよくない?」
と思うかもしれませんが、実務では Stopwatch を使うほうが安全です。
DateTime.Now はシステム時計の変更(NTP 同期、手動変更など)の影響を受けます。Stopwatch は「高精度タイマー」を使っていて、
「実際に経過した時間」を測るのに向いています。
「処理時間」「パフォーマンス計測」のような用途では、
基本的に Stopwatch 一択で考えてOKです。
Restart と Reset の違い
Stopwatch には Restart() と Reset() があります。
Restart() … 経過時間を 0 にして、すぐに再スタートReset() … 経過時間を 0 にするだけ(止まった状態)
例えば、同じインスタンスで何度も計測したいときは Restart() が便利です。
var sw = new Stopwatch();
sw.Restart();
DoWorkA();
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
DoWorkB();
Console.WriteLine(sw.ElapsedMilliseconds);
C#この違いを知っておくと、「あれ、止まってるのに時間が増えてる?」みたいな混乱を避けられます。
まとめ 「ストップウォッチユーティリティ」は“なんとなく”をやめるための武器
ストップウォッチは、
「なんとなく遅い気がする」「たぶん速くなったはず」をやめて、
「何ミリ秒かかった」「何%速くなった」と言えるようにするための道具です。
押さえておきたいポイントはこうです。
Stopwatch.StartNew() → 処理 → Stopwatch.Stop() → Elapsed という基本パターンを体に入れる。
ラップ計測では「累積時間」と「区間時間」を意識して Elapsed を使い分ける。Measure(Action) や MeasureAsync(Func<Task>) のようなユーティリティにしておくと、どこでもすぐ計測できる。
処理時間の計測には DateTime.Now ではなく Stopwatch を使う。Restart と Reset の違いを理解して、同じインスタンスをうまく回す。
ここまで押さえれば、
「なんとなく速い/遅い」から卒業して、
“数字で語れる C# プログラマー”に一歩近づけます。
