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

コンピューティング時間を短縮するとコストが削減されるため、コードを最適化するとお金を節約できます。 このケース スタディでは、パフォーマンスの問題があるサンプル アプリケーションを使用して、効率を向上させるプロファイリング ツールの使用方法を示します。 プロファイリング ツールを比較する場合は、「使用するツール」を参照してください。

このケース スタディでは、次のトピックについて説明します。

  • コードの最適化の重要性と、それがコンピューティング コストの削減に与える影響。
  • Visual Studio プロファイリング ツールを使用して、アプリケーションのパフォーマンスを分析する方法。
  • これらのツールによって提供されるデータを解釈して、パフォーマンスのボトルネックを特定する方法。
  • CPU 使用率、メモリ割り当て、データベースのやり取りに重点を置いて、コードを最適化するための実用的な戦略を適用する方法。

さらにこれらの手法を独自のアプリケーションに適用して、効率とコスト効率を高めます。

最適化のケーススタディ

このケース スタディで検討するサンプル アプリケーションは、ブログとブログ投稿のデータベースに対してクエリを実行する .NET アプリケーションです。 これは、.NET 用の一般的な ORM (オブジェクト リレーショナル マッピング) である Entity Framework を使用して、SQLite ローカル データベースと対話します。 このアプリケーションは、大量のクエリを実行するように構成されており、.NET アプリケーションで膨大なデータ取得タスクを処理する必要がある実際のシナリオをシミュレートします。 サンプル アプリケーションは、Entity Framework 概要サンプルの修正バージョンです。

サンプル アプリケーションの主なパフォーマンスの問題は、コンピューティング リソースの管理方法とデータベースとのやり取り方法にあります。 アプリケーションにはパフォーマンスのボトルネックがあり、それが効率に大きく影響し、結果としてアプリケーションの実行に関連するコンピューティング コストにも影響を及ぼします。 この問題には次の症状が含まれます。

  • 高い CPU 使用率: アプリケーションは、大量の CPU リソースを不必要に消費するような非効率的な評価や処理タスクを実行する場合があります。 これにより、応答時間が遅くなり、運用コストが増加する可能性があります。

  • 非効率的なメモリ割り当て: アプリケーションは、メモリの使用と割り当てに関連する問題に直面することがあります。 .NET アプリでは、メモリ管理が非効率だとガベージ コレクションが増加し、アプリケーションのパフォーマンスに影響する可能性があります。

  • データベース相互作用のオーバーヘッド: データベースに対して大量のクエリを実行するアプリケーションでは、データベース相互作用に関連するボトルネックが発生する可能性があります。 これには、非効率的なクエリ、過剰なデータベース呼び出し、Entity Framework 機能の不適切な使用が含まれ、これらはすべてパフォーマンスを低下させる可能性があります。

このケース スタディでは、Visual Studio のプロファイリング ツールを使用してアプリケーションのパフォーマンスを分析することで、これらの問題に対処することを目的としています。 アプリケーションのパフォーマンスをどこでどのように改善できるかを理解することで、開発者は CPU 使用率を削減し、メモリ割り当ての効率を改善し、データベースのやり取りを合理化し、リソースの使用率を最適化するための最適化を実装できます。 最終的な目標は、アプリケーションの全体的なパフォーマンスを向上させ、実行の効率とコスト効率を高めることです。

課題

サンプル .NET アプリケーションのパフォーマンスの問題に対処するには、いくつかの課題があります。 これらの課題は、パフォーマンスのボトルネックの診断の複雑さから生じます。 説明した問題を解決するための主な課題は次のとおりです。

  • パフォーマンスのボトルネックの診断: 主な課題の 1 つは、パフォーマンスの問題の根本原因を正確に特定することです。 高い CPU 使用率、非効率的なメモリ割り当て、データベース相互作用のオーバーヘッドには、複数の要因が関係している可能性があります。 開発者はプロファイリング ツールを効果的に使用してこれらの問題を診断する必要がありますが、そのためにはこれらのツールの動作方法とその出力の解釈方法をある程度理解している必要があります。

  • 知識とリソースの制約: 最後に、チームは知識、専門知識、リソースに関連する制約に直面する可能性があります。 アプリケーションのプロファイリングと最適化には特定のスキルと経験が必要であり、すべてのチームがこれらのリソースにすぐにアクセスできるとは限りません。

これらの課題に対処するには、プロファイリング ツールの効果的な使用、技術的な知識、慎重な計画とテストを組み合わせた戦略的なアプローチが必要です。 このケーススタディの目的は、開発者にこのプロセスをガイドし、これらの課題を克服してアプリケーションのパフォーマンスを向上させるための戦略と洞察を提供することです。

戦略

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

  • 調査は、CPU 使用率のトレースを取得することから始まります。 Visual Studio の CPU 使用率ツール は、パフォーマンスの調査を開始したり、コードを最適化してコストを削減したりするのに役立ちます。
  • 次に、問題を特定したりパフォーマンスを改善したりするために役立つ追加の洞察を得るために、他のプロファイリング ツールのいずれかを使用してトレースを収集します。 例:
    • メモリ使用量を見てみましょう。 .NET の場合は、まず .NET オブジェクト割り当てツール を試します。 (.NET または C++ の場合は、代わりにメモリ使用量ツールを参照できます。)
    • ADO.NET または Entity Framework の場合、 データベース ツール を使用して、SQL クエリ、正確なクエリ時間などを調べることができます。

データ コレクションには次のタスクが必要です。

  • アプリをリリース ビルドに設定する。
  • パフォーマンス プロファイラー (Alt+F2) から CPU 使用率ツールを選択する。 (後の手順には、他にもいくつかのツールが含まれます。)
  • パフォーマンス プロファイラーからアプリを開始してトレースを収集します。

CPU 使用率が高い部分を検査する

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

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

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

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

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

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

CPU 使用率ツールでセルフ CPU がハイライトされた呼び出しツリー ビューのスクリーンショット。

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

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

追加データを収集する

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

  • まず、メモリ使用量を確認します。 高 CPU 使用率と高メモリ使用量は相関関係にある可能性があるため、問題を特定するために両方を確認することをおすすめします。
  • LINQ DLL を特定したため、データベース ツールも確認します。

メモリ使用量を確認する

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

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

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

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

パフォーマンス プロファイラーでは、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 使用率ツールを再実行してトレースを収集します。 呼び出しツリー ビューでは、 GetBlogTitleX がアプリの CPU 合計の 37% である 1754 ミリ秒しか実行していないことが示され、59% から大幅に改善されています。

CPU 使用率ツールの呼び出しツリー ビューでの、改善した CPU 使用率のスクリーンショット。

改善を示す別の視覚化を確認するには、[フレーム グラフ] ビューに切り替えます。 このビューでも、 GetBlogTitleX で使われる CPU が少なくなっています。

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

データベース ツール トレースで結果を調べると、このクエリを使うと読み取られるレコードの数が 100,000 ではなく 2 つのみになっています。 また、クエリは大幅に簡略化され、以前は生成されていた不要な LEFT JOIN がなくなっています。

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

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

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

繰り返す

複数の最適化が必要になる場合があり、コードの変更を継続的に繰り返して、どの変更によってパフォーマンスが向上し、コンピューティング コストが削減できるのかを確認できます。

次のステップ

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