次の方法で共有


コード最適化とコンピューティング コスト削減の初級者向けガイド (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 クエリ、正確なクエリ時間などを調べることができます。

データ収集には次の手順が必要です。

  • アプリをリリース ビルドに設定しました。
  • Performance Profiler (Alt+F2) から CPU 使用率ツールを選択します。 (後の手順には、他にもいくつかのツールが含まれます。)
  • Performance Profiler からアプリを起動し、トレースを収集します。

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

CPU 使用率ツールでトレースを収集し、それを Visual Studio に読み込んだ後、まずトップ インサイトとホット パスを表示する最初の .diagsession レポート ページを確認します。 ホット パスには、アプリ内で CPU 使用率が最も高いコード パスが表示されます。 これらのセクションでは、改善できるパフォーマンスの問題を迅速に特定するのに役立つヒントが提供される場合があります。

コールツリー ビューでホットパスを表示することもできます。 このビューを開くには、レポートの [詳細を開く] リンクを使って、 [コールツリー]を選びます。

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

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

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

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

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

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

追加データを収集する

多くの場合、他のツールは、分析に役立つ追加情報を提供し、問題を特定できます。 この例では、以下のような方法で行います。

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

メモリ使用量を確認する

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

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

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

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

Performance Profiler では、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 クエリを最適化する方法に関する一般的なレコメンデーションがいくつか見つかり、このコードが作成されました。

foreach (var x in db.Posts.Where(p => p.Author.Contains("Fred Smith")).Select(b => b.Title).ToList())
{
  Console.WriteLine("Post: " + x);
}

このコードでは、クエリを最適化するためにいくつかの変更を加えました。

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

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

結果

コードを更新した後、CPU 使用率ツールを再実行してトレースを収集します。 呼び出しツリー ビューでは、 GetBlogTitleX がアプリの CPU 合計の 37% である 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 パフォーマンス ツールの効果的な使用方法を学習するのに役立つ情報を提供しています。