次の方法で共有


ケース スタディ: コードの最適化とコンピューティング コストの削減に関する初心者向けガイド (C#、Visual Basic、C++、F#)

コードを最適化すると、コンピューティング時間とコストが削減されます。 このケース スタディでは、Visual Studio プロファイリング ツールを使用して、サンプル .NET アプリケーションのパフォーマンスの問題を特定して修正する方法を示します。 プロファイリング ツールを比較する場合は、「どのツールを選択すればよいか」を参照してください。

このガイドでは、次の内容について説明します。

  • Visual Studio プロファイリング ツールを使用してパフォーマンスを分析および向上させる方法。
  • CPU 使用率、メモリ割り当て、データベース操作を最適化するための実用的な戦略。

これらの手法を適用して、独自のアプリケーションをより効率的にします。

最適化のケース スタディ

サンプルの .NET アプリケーションでは、Entity Framework を使用してブログと投稿の SQLite データベースに対してクエリを実行します。 多くのクエリを実行し、実際のデータ取得シナリオをシミュレートします。 このアプリは Entity Framework の概要サンプルに基づいていますが、より大きなデータセットを使用します。

パフォーマンスに関する主な問題は次のとおりです。

  • CPU 使用率が高い: 非効率的な計算や処理タスクにより、CPU の消費量とコストが増加します。
  • 非効率的なメモリ割り当て: メモリ管理が不十分な場合、過剰なガベージ コレクションが発生し、パフォーマンスが低下します。
  • データベースのオーバーヘッド: 非効率的なクエリと過剰なデータベース呼び出しにより、パフォーマンスが低下します。

このケース スタディでは、Visual Studio プロファイリング ツールを使用してこれらの問題を特定して対処し、アプリケーションの効率とコスト効率を高めます。

課題

これらのパフォーマンスの問題を解決するには、いくつかの課題があります。

  • ボトルネックの診断: CPU、メモリ、またはデータベースのオーバーヘッドが高い根本原因を特定するには、プロファイリング ツールを効果的に使用し、結果を正しく解釈する必要があります。
  • 知識とリソースの制約: プロファイリングと最適化には特定のスキルと経験が必要であり、常に使用できるとは限りません。

これらの課題を克服するには、プロファイリング ツール、技術的な知識、慎重なテストを組み合わせた戦略的アプローチが不可欠です。

戦略

このケース スタディのアプローチの概要を次に示します。

  • Visual Studio の CPU 使用率ツールを使用して 、CPU 使用率トレースから開始します。 Visual Studio の CPU 使用率ツールは、パフォーマンス調査の出発点として適しています。
  • メモリとデータベース分析のために追加のトレースを収集します。

データ収集には、次のタスクが必要です。

  • アプリをリリース ビルドに設定します。
  • パフォーマンス プロファイラー (Alt + F2) で CPU 使用率ツールを選択します。
  • パフォーマンス プロファイラーで、アプリを起動し、トレースを収集します。

CPU 使用率が高い領域を検査する

CPU 使用率ツールを使ってトレースを収集し、Visual Studio に読み込んだ後、概要データを示す .diagsession レポートページを最初に確認します。 レポートの [詳細を開く] リンクを使用します。

CPU 使用率ツールで詳細を開くスクリーンショット。

レポートの詳細ビューで、呼び出しツリー ビューを開きます。 アプリの CPU 使用率が最も高いコード パスは、ホット パスと呼ばれます。 ホットパスフレームアイコン (ホットパスアイコンを示すスクリーンショット。) は、改善の見込みのあるパフォーマンス問題をすばやく特定するのに役立ちます。

呼び出しツリー ビューでは、アプリ内の GetBlogTitleX メソッドがアプリの CPU 使用率の約 60% を占めているため、CPU 使用率が高いことがわかります。 しかし、GetBlogTitleX 値は低く、約 0.10%です。 合計 CPUとは異なり、セルフ CPU 値は他の関数で費やされた時間を除外するため、呼び出しツリーの下で実際のボトルネックを調べることができます。

CPU 使用率ツールのコール ツリー ビューのスクリーンショット。

GetBlogTitleX は、2 つの LINQ DLL に対して外部呼び出しを行います。これは CPU 時間の大部分を使用しています。これは、非常に高い セルフ CPU 値によって証明されます。 これは、LINQ クエリが最適化する領域になる可能性がある最初の手掛かりです。

[自己 CPU] が強調表示されている [CPU 使用率] ツールの [コール ツリー] ビューのスクリーンショット。

視覚化された呼び出しツリーとデータの別のビューを取得するには、Flame Graph ビューを開きます。 (または、GetBlogTitleX を右クリックし、[Flame Graph 表示] を選択します)。ここでも、GetBlogTitleX メソッドがアプリの CPU 使用率の多くを担当しているようです (黄色で表示)。 LINQ DLL の外部呼び出しが GetBlogTitleX ボックスの下に表示され、メソッドのすべての CPU 時間を使用しています。

CPU 使用率ツールの [Flame Graph] ビューのスクリーンショット。

追加データを収集する

多くの場合、他のツールは、分析に役立つ追加情報を提供し、問題を特定できます。 このケース スタディでは、次のアプローチを取ります。

  • まず、メモリ使用量を確認します。 CPU 使用率が高い場合とメモリ使用量が多い場合は、相関関係がある可能性があるため、両方を確認して問題を特定すると役立ちます。
  • LINQ DLL を特定したため、データベース ツールについても説明します。

メモリ使用量を確認する

メモリ使用量の観点からアプリで何が起こっているかを確認するには、.NET オブジェクト割り当てツールを使用してトレースを収集します (C++ の場合は、代わりにメモリ使用量ツールを使用できます)。 メモリ トレースの 呼び出しツリー ビューにはホット パスが表示され、メモリ使用量が多い領域を特定するのに役立ちます。 この時点で、GetBlogTitleX メソッドは多くのオブジェクトを生成しているように見えます。 実際には、900,000 を超えるオブジェクト割り当て。

.NET オブジェクト割り当てツールのコール ツリー ビューのスクリーンショット。

作成されるオブジェクトのほとんどは、文字列、オブジェクト配列、および Int32s です。 ソース コードを調べることで、これらの型がどのように生成されるかを確認できる場合があります。

データベース ツールでクエリを確認する

パフォーマンス プロファイラーでは、CPU 使用率ではなくデータベース ツールを選択します (または、両方を選択します)。 トレースを収集したら、診断ページの クエリ タブを開きます。 データベース トレースの [クエリ] タブで、最初の行に最長のクエリ (2446 ミリ秒) が表示されます。 レコード 列には、クエリが読み取るレコードの数が表示されます。 この情報は、後で比較するために使用できます。

データベース ツールのデータベース クエリのスクリーンショット。

LINQ によって生成された SELECT ステートメントをクエリ列で調べることで、最初の行を GetBlogTitleX メソッドに関連付けられたクエリとして識別します。 完全なクエリ文字列を表示するには、列の幅を展開します。 完全なクエリ文字列は次のとおりです。

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

アプリでは、必要以上に多くの列値がここで取得されていることに注意してください。 ソース コードを見てみましょう。

コードを最適化する

GetBlogTitleX ソース コードを見てみましょう。 データベース ツールで、クエリを右クリックし、[ソース ファイルに移動] 選択します。 GetBlogTitleXのソース コードでは、LINQ を使用してデータベースを読み取る次のコードを見つけます。

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

このコードでは、foreach ループを使用して、作成者として "Fred Smith" を含むブログをデータベースで検索します。 これを見ると、多数のオブジェクトがメモリ内で生成されていることがわかります。データベース内の各ブログの新しいオブジェクト配列、各 URL の関連付けられた文字列、投稿に含まれるプロパティの値 (ブログ ID など)。

少し調査を行い、LINQ クエリを最適化する方法に関するいくつかの一般的な推奨事項を見つけます。 時間を節約して Copilot に調査を任せることもできます。

Copilot を使用している場合は、コンテキスト メニュー [Copilot に質問する] を選択し、次の質問を入力します。

Can you make the LINQ query in this method faster?

ヒント

/optimize などのスラッシュ コマンドを使用して、Copilot に関する適切な質問を作成できます。

この例では、Copilot は、次の推奨されるコード変更と説明を提供します。

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

このコードには、クエリの最適化に役立ついくつかの変更が含まれています。

  • Where 句を追加し、foreach ループの 1 つを削除しました。
  • Select ステートメントでは Title プロパティのみを投影しました。これはこの例で必要なすべてです。

次に、プロファイリング ツールを使用して再テストします。

結果

コードを更新した後、CPU 使用率ツールを再実行してトレースを収集します。 呼び出しツリーの ビューでは、アプリの CPU 合計の 37% を使用して、GetBlogTitleX が 1754 ミリ秒しか実行されていないことが示されています。これは、59%からの大幅な改善です。

CPU 使用率ツールの [呼び出しツリー] ビューでの CPU 使用率の向上のスクリーンショット。

Flame Graph ビューに切り替えて、改善点を示す別の視覚化を表示します。 このビューでは、GetBlogTitleX は CPU の小さな部分も使用します。

CPU 使用率ツールの [フレーム グラフ] ビューでの CPU 使用率の改善のスクリーンショット。

データベース ツール トレースの結果を確認します。100,000 ではなく、このクエリを使用して読み取られたレコードは 2 つだけです。 また、クエリは大幅に簡略化され、以前に生成された不要な LEFT JOIN が不要になります。

データベース ツールでのクエリ時間の短縮のスクリーンショット。

次に、.NET オブジェクト割り当てツールの結果を再確認し、GetBlogTitleX が 56,000 個のオブジェクト割り当てのみを担当し、900,000 から約 95% 削減されることを確認します。

.NET オブジェクト割り当てツールでのメモリ割り当ての削減のスクリーンショット。

繰り返す

複数の最適化が必要な場合があり、コードの変更を反復処理し続けて、パフォーマンスを向上させ、コンピューティング コストを削減するのに役立つ変更を確認できます。

次の手順

次の記事とブログ記事では、Visual Studio パフォーマンス ツールを効果的に使用する方法を学習するのに役立つ詳細情報を提供します。