はじめに 「実行時間計測」は“なんとなく”を数字に変える技術
「この処理、重そうだけど本当にボトルネックなのか?」
「リファクタリングしたけど、速くなったって言い切れる?」
こういうときに必要なのが「実行時間計測」です。
感覚ではなく“数字”で語れるようになると、コードの議論の質が一気に上がります。
C# では、実行時間計測の主役は System.Diagnostics.Stopwatch です。
ここでは、単発計測から、ユーティリティ化、同期・非同期両対応まで、
初心者向けに順番にかみ砕いていきます。
基本形:Stopwatch を使った最小の実行時間計測
「このメソッドに何ミリ秒かかるか」を測る
まずは、一番ストレートな計測パターンです。
using System;
using System.Diagnostics;
public class ExecutionTimeSample
{
public static void Main()
{
var sw = Stopwatch.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#ここで絶対に押さえてほしいのは次の流れです。
Stopwatch.StartNew() で「インスタンス生成+計測開始」。
計測したい処理を実行。Stopwatch.Stop() で止めて、Elapsed / ElapsedMilliseconds を読む。
Elapsed は TimeSpan なので、TotalMilliseconds や TotalSeconds など、好きな単位で取り出せます。
ユーティリティ化:処理を渡すと時間を返してくれるメソッド
Action を受け取る Measure メソッド
毎回 Stopwatch を手で書くのは面倒なので、
「処理を渡すと、実行時間を TimeSpan で返してくれる」ユーティリティを作っておくと便利です。
using System;
using System.Diagnostics;
public static class ExecutionTimer
{
public static TimeSpan Measure(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var sw = Stopwatch.StartNew();
action();
sw.Stop();
return sw.Elapsed;
}
}
C#使い方の例です。
TimeSpan elapsed = ExecutionTimer.Measure(() =>
{
DoHeavyWork();
});
Console.WriteLine($"処理時間: {elapsed.TotalMilliseconds} ms");
C#この形を一度作っておくと、
「ちょっとこの処理の時間を見たい」が一行で書けるようになります。
非同期処理の実行時間計測(async/await 対応)
Func<Task> を受け取る MeasureAsync
最近の業務コードは async/await が当たり前なので、
非同期メソッドの実行時間も測れるようにしておきたいです。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
public static class ExecutionTimerAsync
{
public static async Task<TimeSpan> MeasureAsync(Func<Task> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
var sw = Stopwatch.StartNew();
await action();
sw.Stop();
return sw.Elapsed;
}
}
C#使い方の例です。
TimeSpan elapsed = await ExecutionTimerAsync.MeasureAsync(async () =>
{
await Task.Delay(1000); // 疑似的に1秒かかる処理
});
Console.WriteLine($"非同期処理時間: {elapsed.TotalMilliseconds} ms");
C#ポイントは、「await の前後を Stop/Start で挟む」のではなく、await を含む処理全体を MeasureAsync に渡してしまうことです。
これで「その非同期処理が完了するまでの実時間」が測れます。
ラベル付き計測:ログに「どの処理か」も一緒に出す
名前とセットで計測する
実務では、「どの処理の時間か」が分からないとログが読みにくくなります。
そこで、ラベル(名前)付きで計測して、そのままログに出すユーティリティもよく使います。
public static class ExecutionTimerWithLog
{
public static void MeasureAndLog(string name, Action action)
{
TimeSpan elapsed = ExecutionTimer.Measure(action);
Console.WriteLine($"[{name}] {elapsed.TotalMilliseconds} ms");
}
}
C#使い方の例です。
ExecutionTimerWithLog.MeasureAndLog("ユーザー一覧取得", () =>
{
LoadUsers();
});
ExecutionTimerWithLog.MeasureAndLog("レポート生成", () =>
{
GenerateReport();
});
C#こうしておくと、ログを見たときに
[ユーザー一覧取得] 123.45 ms
[レポート生成] 987.65 ms
のように、「どの処理がどれくらい重いか」が一目で分かります。
実務での重要ポイント:DateTime ではなく Stopwatch を使う理由
システム時計の変更に影響されない
「開始時刻を DateTime.Now で取って、終了時刻との差を取ればよくない?」
と考えがちですが、実行時間計測には Stopwatch を使うほうが圧倒的に安全です。
DateTime.Now は OS のシステム時計を見ているので、
NTP 同期や手動変更で時間が戻ったり進んだりすると、
「マイナス時間」や「異常に長い時間」が出てしまうことがあります。
Stopwatch は、OS が提供する高精度タイマーを使っていて、
「実際に経過した時間」を測るのに向いています。
処理時間・パフォーマンス計測・ベンチマークのような用途では、
基本的に「Stopwatch 一択」と覚えてしまって構いません。
もう一歩:複数回実行して平均を取る
一回だけだとブレるので、回数を決めて測る
実行時間は、CPU の状態や他プロセスの影響でブレます。
「本当に速くなったか」を確かめたいときは、
同じ処理を複数回実行して平均を取ると、より信頼できる数字になります。
public static TimeSpan MeasureAverage(Action action, int count)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count));
long totalTicks = 0;
for (int i = 0; i < count; i++)
{
var sw = Stopwatch.StartNew();
action();
sw.Stop();
totalTicks += sw.ElapsedTicks;
}
long averageTicks = totalTicks / count;
return new TimeSpan(averageTicks);
}
C#使い方の例です。
TimeSpan avg = MeasureAverage(() => DoHeavyWork(), 5);
Console.WriteLine($"平均処理時間: {avg.TotalMilliseconds} ms (5回実行)");
C#ここでのポイントは、「ElapsedMilliseconds ではなく ElapsedTicks を合計している」ことです。
ティック(最小単位)で合計してから平均を出すほうが、丸め誤差が少なくなります。
まとめ 「実行時間計測ユーティリティ」は“感覚ではなく数字で語るための基盤”
実行時間計測は、
「なんとなく遅い」「たぶん速くなった」を卒業して、
「◯ms かかっている」「◯%改善した」と言えるようにするための基盤です。
押さえておきたいポイントを整理すると、こうなります。
基本は Stopwatch.StartNew() → 処理 → Stopwatch.Stop() → Elapsed。
毎回書かずに、Measure(Action) や MeasureAsync(Func<Task>) のようなユーティリティにしてしまう。
ログに出すなら「処理名」とセットで計測すると、あとから見返しやすい。
処理時間計測には DateTime.Now ではなく Stopwatch を使う。
ブレを減らしたいときは、複数回実行して平均を取る。
ここまで身につけば、
「実行時間を測る」という行為が特別なことではなくなり、
日常的に“数字でコードを評価できる”エンジニアに近づいていきます。

