コードを最適化すると、コンピューティング時間とコストが削減されます。 このケース スタディでは、Visual Studio プロファイリング ツールを使用して、サンプル .NET アプリケーションのパフォーマンスの問題を特定して修正する方法を示します。 プロファイリング ツールを比較する場合は、「どのツールを選択すればよいか」を参照してください。
このガイドでは、次の内容について説明します。
- Visual Studio プロファイリング ツールを使用してパフォーマンスを分析および向上させる方法。
- CPU 使用率、メモリ割り当て、データベース操作を最適化するための実用的な戦略。
これらの手法を適用して、独自のアプリケーションをより効率的にします。
最適化のケース スタディ
サンプルの .NET アプリケーションでは、Entity Framework を使用してブログと投稿の SQLite データベースに対してクエリを実行します。 多くのクエリを実行し、実際のデータ取得シナリオをシミュレートします。 このアプリは Entity Framework の概要サンプルに基づいていますが、より大きなデータセットを使用します。
パフォーマンスに関する主な問題は次のとおりです。
- CPU 使用率が高い: 非効率的な計算や処理タスクにより、CPU の消費量とコストが増加します。
- 非効率的なメモリ割り当て: メモリ管理が不十分な場合、過剰なガベージ コレクションが発生し、パフォーマンスが低下します。
- データベースのオーバーヘッド: 非効率的なクエリと過剰なデータベース呼び出しにより、パフォーマンスが低下します。
このケース スタディでは、Visual Studio プロファイリング ツールを使用してこれらの問題を特定して対処し、アプリケーションの効率とコスト効率を高めます。
課題
これらのパフォーマンスの問題を解決するには、いくつかの課題があります。
- ボトルネックの診断: CPU、メモリ、またはデータベースのオーバーヘッドが高い根本原因を特定するには、プロファイリング ツールを効果的に使用し、結果を正しく解釈する必要があります。
- 知識とリソースの制約: プロファイリングと最適化には特定のスキルと経験が必要であり、常に使用できるとは限りません。
これらの課題を克服するには、プロファイリング ツール、技術的な知識、慎重なテストを組み合わせた戦略的アプローチが不可欠です。
戦略
このケース スタディのアプローチの概要を次に示します。
- Visual Studio の CPU 使用率ツールを使用して 、CPU 使用率トレースから開始します。 Visual Studio の CPU 使用率ツールは、パフォーマンス調査の出発点として適しています。
- メモリとデータベース分析のために追加のトレースを収集します。
- メモリ分析情報には 、.NET オブジェクト割り当てツール を使用します。
- データベース ツールを使用して、SQL クエリとタイミングを調べます。
データ収集には、次のタスクが必要です。
- アプリをリリース ビルドに設定します。
- パフォーマンス プロファイラー (Alt + F2) で CPU 使用率ツールを選択します。
- パフォーマンス プロファイラーで、アプリを起動し、トレースを収集します。
CPU 使用率が高い領域を検査する
CPU 使用率ツールを使ってトレースを収集し、Visual Studio に読み込んだ後、概要データを示す .diagsession レポートページを最初に確認します。 レポートの [詳細を開く] リンクを使用します。
レポートの詳細ビューで、呼び出しツリー ビューを開きます。 アプリの CPU 使用率が最も高いコード パスは、ホット パスと呼ばれます。 ホットパスフレームアイコン (ホットパスアイコンを示す) は、改善の見込みのあるパフォーマンス問題をすばやく特定するのに役立ちます。
呼び出しツリー ビューでは、アプリ内の GetBlogTitleX
メソッドがアプリの CPU 使用率の約 60% を占めているため、CPU 使用率が高いことがわかります。 しかし、 の GetBlogTitleX
値は低く、約 0.10%です。 合計 CPUとは異なり、セルフ CPU 値は他の関数で費やされた時間を除外するため、呼び出しツリーの下で実際のボトルネックを調べることができます。
GetBlogTitleX
は、2 つの LINQ DLL に対して外部呼び出しを行います。これは CPU 時間の大部分を使用しています。これは、非常に高い セルフ CPU 値によって証明されます。 これは、LINQ クエリが最適化する領域になる可能性がある最初の手掛かりです。
視覚化された呼び出しツリーとデータの別のビューを取得するには、Flame Graph ビューを開きます。 (または、GetBlogTitleX
を右クリックし、[Flame Graph 表示] を選択します)。ここでも、GetBlogTitleX
メソッドがアプリの CPU 使用率の多くを担当しているようです (黄色で表示)。 LINQ DLL の外部呼び出しが GetBlogTitleX
ボックスの下に表示され、メソッドのすべての CPU 時間を使用しています。
追加データを収集する
多くの場合、他のツールは、分析に役立つ追加情報を提供し、問題を特定できます。 このケース スタディでは、次のアプローチを取ります。
- まず、メモリ使用量を確認します。 CPU 使用率が高い場合とメモリ使用量が多い場合は、相関関係がある可能性があるため、両方を確認して問題を特定すると役立ちます。
- LINQ DLL を特定したため、データベース ツールについても説明します。
メモリ使用量を確認する
メモリ使用量の観点からアプリで何が起こっているかを確認するには、.NET オブジェクト割り当てツールを使用してトレースを収集します (C++ の場合は、代わりにメモリ使用量ツールを使用できます)。 メモリ トレースの 呼び出しツリー ビューにはホット パスが表示され、メモリ使用量が多い領域を特定するのに役立ちます。 この時点で、GetBlogTitleX
メソッドは多くのオブジェクトを生成しているように見えます。 実際には、900,000 を超えるオブジェクト割り当て。
作成されるオブジェクトのほとんどは、文字列、オブジェクト配列、および 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%からの大幅な改善です。
Flame Graph ビューに切り替えて、改善点を示す別の視覚化を表示します。 このビューでは、GetBlogTitleX
は CPU の小さな部分も使用します。
データベース ツール トレースの結果を確認します。100,000 ではなく、このクエリを使用して読み取られたレコードは 2 つだけです。 また、クエリは大幅に簡略化され、以前に生成された不要な LEFT JOIN が不要になります。
次に、.NET オブジェクト割り当てツールの結果を再確認し、GetBlogTitleX
が 56,000 個のオブジェクト割り当てのみを担当し、900,000 から約 95% 削減されることを確認します。
繰り返す
複数の最適化が必要な場合があり、コードの変更を反復処理し続けて、パフォーマンスを向上させ、コンピューティング コストを削減するのに役立つ変更を確認できます。
次の手順
次の記事とブログ記事では、Visual Studio パフォーマンス ツールを効果的に使用する方法を学習するのに役立つ詳細情報を提供します。
- ケース スタディ: パフォーマンスの問題 を分離する
- ケース スタディ: 30 分以内で 2 倍のパフォーマンス
- 新しいインストルメンテーション ツール を使用して Visual Studio のパフォーマンスを向上させる
関連コンテンツ
- どのツールを使用する必要がありますか?
- パフォーマンス プロファイラー で CPU 使用率を分析する
- メモリ分析ツールの選択
- データベースパフォーマンス を分析する
- プロファイリングでのはじめに