C# Tips | ログ・例外・診断:パフォーマンス計測

C# C#
スポンサーリンク

はじめに:パフォーマンス計測は「コードの速さを“感覚”ではなく“数字”で語るための道具」

業務システムを書いていると、必ずこういう会話が出てきます。

「この処理、なんか遅くない?」
「どれくらい遅いんだっけ?」
「前より速くなったって言える根拠ある?」

ここで「体感的に速くなった気がする」では、説得力が弱いんですよね。
だからこそ パフォーマンス計測 が必要になります。

パフォーマンス計測の本質は、

「処理にかかった時間を、ミリ秒や秒といった“数字”で記録し、
どこがボトルネックか、改善でどれだけ良くなったかを、客観的に語れるようにする」

ことです。

ここでは、初心者向けに

Stopwatch を使った基本的な計測
「区間ごとに計測する」考え方
ログと組み合わせたパフォーマンス計測ユーティリティ
非同期処理の計測

を、例題付きでかみ砕いて説明します。


パフォーマンス計測の基本:Stopwatch を使う

「スタートして、終わったら止めて、経過時間を見る」

C# でパフォーマンス計測をするときの基本ツールが System.Diagnostics.Stopwatch です。
ストップウォッチという名前の通り、「開始」「停止」「経過時間の取得」ができます。

一番シンプルな例から見てみましょう。

using System.Diagnostics;

public void DoWork()
{
    var sw = Stopwatch.StartNew();  // 計測開始

    // 計測したい処理
    HeavyWork();

    sw.Stop();                      // 計測終了

    Console.WriteLine($"HeavyWork にかかった時間: {sw.ElapsedMilliseconds} ms");
}
C#

Stopwatch.StartNew() は「インスタンスを作ってすぐ計測開始」してくれる便利メソッドです。
処理が終わったら Stop() を呼び、ElapsedMillisecondsElapsed(TimeSpan)で経過時間を取り出します。

ここでの重要ポイントは、

「パフォーマンス計測は“処理の前後を挟む”だけで始められる」
「まずは“どれくらいかかっているか”を知ることがスタートライン」

ということです。
最初から難しいことを考えなくてよくて、「とりあえず Stopwatch で測ってみる」が正解です。


区間ごとに計測する:どこが遅いかを切り分ける

「処理全体」だけでなく「中のステップ」を測る

「このメソッドが遅い」ということが分かったら、次に知りたいのは「どの部分が遅いのか」です。
そこで効いてくるのが「区間ごとの計測」です。

例えば、ユーザー登録処理を考えてみます。

public void RegisterUser(UserRequest request)
{
    var sw = Stopwatch.StartNew();

    Validate(request);          // 入力チェック
    var user = MapToEntity(request); // マッピング
    SaveToDatabase(user);       // DB 保存

    sw.Stop();
    Console.WriteLine($"RegisterUser 全体: {sw.ElapsedMilliseconds} ms");
}
C#

これだと「全体の時間」は分かりますが、「どこが遅いか」は分かりません。
そこで、区間ごとに計測します。

public void RegisterUser(UserRequest request)
{
    var total = Stopwatch.StartNew();

    var swValidate = Stopwatch.StartNew();
    Validate(request);
    swValidate.Stop();

    var swMap = Stopwatch.StartNew();
    var user = MapToEntity(request);
    swMap.Stop();

    var swSave = Stopwatch.StartNew();
    SaveToDatabase(user);
    swSave.Stop();

    total.Stop();

    Console.WriteLine($"RegisterUser 全体: {total.ElapsedMilliseconds} ms");
    Console.WriteLine($"  Validate: {swValidate.ElapsedMilliseconds} ms");
    Console.WriteLine($"  MapToEntity: {swMap.ElapsedMilliseconds} ms");
    Console.WriteLine($"  SaveToDatabase: {swSave.ElapsedMilliseconds} ms");
}
C#

これで、「DB 保存が 90% の時間を食っている」といったことが分かるようになります。

ここでの重要ポイントは、

「パフォーマンス計測は“どこが遅いかを切り分ける”ために使う」
「全体だけでなく、ステップごとに測ることで、改善の優先順位が見えてくる」

ということです。


ログと組み合わせたパフォーマンス計測ユーティリティ

「計測結果をログに残して、後から分析できるようにする」

コンソールに出すだけだと、その場限りで終わってしまいます。
業務システムでは、ログにパフォーマンス情報を残しておくと、後から「どの処理が重いか」を分析できて便利です。

ILogger と組み合わせた小さなユーティリティを作ってみます。

using System.Diagnostics;
using Microsoft.Extensions.Logging;

public static class PerfLogger
{
    public static void Measure(string name, Action action, ILogger logger)
    {
        var sw = Stopwatch.StartNew();

        action();

        sw.Stop();

        logger.LogInformation(
            "Perf: {Name} Elapsed={Elapsed} ms",
            name,
            sw.ElapsedMilliseconds);
    }

    public static T Measure<T>(string name, Func<T> func, ILogger logger)
    {
        var sw = Stopwatch.StartNew();

        var result = func();

        sw.Stop();

        logger.LogInformation(
            "Perf: {Name} Elapsed={Elapsed} ms",
            name,
            sw.ElapsedMilliseconds);

        return result;
    }
}
C#

使い方の例です。

PerfLogger.Measure("RegisterUser", () => RegisterUser(request), _logger);

var user = PerfLogger.Measure("LoadUser", () => LoadUser(id), _logger);
C#

ログには、例えばこんな行が残ります。

「Perf: RegisterUser Elapsed=123 ms」
「Perf: LoadUser Elapsed=45 ms」

ここでの重要ポイントは、

「パフォーマンス計測をユーティリティに閉じ込めることで、
“測る”という行為を簡単に、かつ一貫したフォーマットで行えるようにする」

ということです。
ログ分析ツールと組み合わせれば、「RegisterUser の平均時間」「最大時間」なども簡単に集計できます。


非同期処理のパフォーマンス計測

async/await でも考え方は同じ

今どきのコードは async が多いので、非同期版のパフォーマンス計測も用意しておくと便利です。

public static class PerfLoggerAsync
{
    public static async Task MeasureAsync(
        string name,
        Func<Task> action,
        ILogger logger)
    {
        var sw = Stopwatch.StartNew();

        await action();

        sw.Stop();

        logger.LogInformation(
            "Perf: {Name} Elapsed={Elapsed} ms",
            name,
            sw.ElapsedMilliseconds);
    }

    public static async Task<T> MeasureAsync<T>(
        string name,
        Func<Task<T>> func,
        ILogger logger)
    {
        var sw = Stopwatch.StartNew();

        var result = await func();

        sw.Stop();

        logger.LogInformation(
            "Perf: {Name} Elapsed={Elapsed} ms",
            name,
            sw.ElapsedMilliseconds);

        return result;
    }
}
C#

使い方の例です。

await PerfLoggerAsync.MeasureAsync(
    "CallExternalApi",
    async () => await CallExternalApiAsync(),
    _logger);

var user = await PerfLoggerAsync.MeasureAsync(
    "LoadUserFromApi",
    async () => await LoadUserFromApiAsync(id),
    _logger);
C#

ここでの重要ポイントは、

「非同期でも“前後を挟む”だけで計測できる」
「同期版と非同期版で同じフォーマット・同じルールを使うと、ログが見やすくなる」

ということです。


実務での使いどころと注意点

「常にオン」ではなく「必要なときにオンにする」

パフォーマンス計測は便利ですが、やりすぎるとそれ自体が負荷になることがあります。
特に、全リクエスト・全メソッドに対してミリ秒単位のログを取り続けると、ログ量が膨大になり、
ログ書き込みがボトルネックになることもあります。

そこで大事なのは、「どこで計測するか」を選ぶことです。

明らかに重そうな処理(外部 API、DB 集約、バッチ処理など)
ユーザーから「遅い」と言われている画面
最近改修したばかりで、パフォーマンスが気になる箇所

こういったところに絞って計測し、
問題が解決したら「常時オン」から「必要なときだけオン」に切り替える、という運用もよくあります。

ここでの重要ポイントは、

「パフォーマンス計測は“スポットライト”のように使う」
「常に全体を照らすのではなく、気になるところに当てて、終わったら少し絞る」

という感覚です。


まとめ:パフォーマンス計測は“感覚を数字に変える”ためのユーティリティ

パフォーマンス計測の本質を一言で言うと、

「処理の速さ・遅さを、
“なんとなく”ではなく、
ミリ秒という数字で語れるようにする」

ことです。

押さえておきたいポイントを整理すると、

Stopwatch で処理の前後を挟めば、すぐに時間を測れる。
全体だけでなく、ステップごとに計測することで、どこがボトルネックかが見えてくる。
ILogger と組み合わせたパフォーマンス計測ユーティリティを作ると、ログに一貫した形で残せる。
非同期処理でも同じ考え方で計測できるように、MeasureAsync 形を用意しておくと便利。
計測は「気になるところにスポットライトを当てる」イメージで使い、やりすぎて自分で自分を重くしない。

ここまで腹落ちしていれば、「なんか遅い気がする」から一歩進んで、
“数字で語れるパフォーマンス改善”に踏み出せるようになります。

タイトルとURLをコピーしました