コード最適化とコンピューティング コスト削減の初級者向けガイド (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 値によって証明されるように、 GetBlogTitleX
では 2 つの LINQ DLL への外部呼び出しが行われ、CPU 時間の大部分を使用しています。 これは、最適化する領域として LINQ クエリを探す必要があることを示す最初の手がかりです。
視覚化された呼び出しツリーとデータの別のビューを取得するには、 GetBlogTitleX
を右クリックして [Flame Graph で表示]を選択します。 ここでも、アプリの CPU 使用率の多くは GetBlogTitleX
メソッドによるものであるようです (黄色で表示)。 GetBlogTitleX
ボックスの下に示されている LINQ DLL の外部呼び出しで、メソッドのすべての CPU 時間が使われています。
追加データを収集する
多くの場合、他のツールは、分析に役立つ追加情報を提供し、問題を特定できます。 この例では、以下のような方法で行います。
- まず、メモリ使用量を確認します。 高 CPU 使用率と高メモリ使用量は相関関係にある可能性があるため、問題を特定するために両方を確認することをおすすめします。
- LINQ DLL を特定したため、データベース ツールも確認します。
メモリ使用量を確認する
メモリ使用量の観点からアプリで何が起こっているかを確認するには、.NET オブジェクト割り当てツールを使用してトレースを収集します (C++ の場合は、代わりにメモリ使用量ツールを使用できます)。 メモリ トレースの 呼び出しツリー ビューにはホット パスが表示され、メモリ使用量が多い領域を特定するのに役立ちます。 もう驚くことではありませんが、 GetBlogTitleX
メソッドで多くのオブジェクトが生成されているように見えます。 実際、900,000 を超えるオブジェクトが割り当てられています。
作成されるオブジェクトのほとんどは、文字列、オブジェクト配列、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% から大幅に改善されています。
改善の別の視覚化を確認するには、 Flame Graph ビューに切り替えます。 このビューでも、 GetBlogTitleX
で使われる CPU が少なくなっています。
データベース ツール トレースで結果を確認すると、このクエリを使用して読み取られるレコードは 100,000 件ではなく 2 件のみです。 また、クエリは大幅に簡略化され、以前は生成されていた不要な LEFT JOIN がなくなっています。
次に、.NET オブジェクト割り当てツールで結果を再確認すると、 GetBlogTitleX
が 56,000 個のオブジェクト割り当てのみを担当していることがわかります。これは、900,000 個からほぼ 95% の削減です!
繰り返す
複数の最適化が必要になる場合があり、コードの変更を継続的に繰り返して、どの変更によってパフォーマンスが向上し、コンピューティング コストが削減されるかを確認できます。
次のステップ
次の記事とブログ投稿では、Visual Studio パフォーマンス ツールの効果的な使用方法を学習するのに役立つ情報を提供しています。
- パフォーマンスの問題の特定
- ケース スタディ: 30 分以内でパフォーマンスを 2 倍にする
- 新しいインストルメンテーション ツールを使用して Visual Studio のパフォーマンスを向上させる
関連するコンテンツ
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示