コード最適化とコンピューティング コスト削減の初級者向けガイド (C#、Visual Basic、C++、F#)

コンピューティング時間を短縮するとコストが削減されるため、コードを最適化するとお金を節約できます。 この記事では、さまざまなプロファイリング ツールを使用してこのタスクを実行する方法について説明します。

ここでは、手順を追って説明するのではなく、プロファイリング ツールを効果的に使用する方法とデータを解釈する方法を示します。 CPU 使用率ツールは、アプリケーション内でコンピューティング リソースが使われている場所をキャプチャして視覚化するのに役立ちます。 呼び出しツリーやフレーム グラフなどの CPU 使用率ビューでは、アプリ内で時間がかかっている箇所がわかりやすいグラフィックで示されます。 さらに、自動分析情報では、大きな影響を与える可能性のある正確な最適化が示される場合があります。 その他のプロファイリング ツールも、問題を特定するのに役立ちます。 ツールの比較については、「使用するパフォーマンス ツール」の記事をご覧ください。

調査を開始する

  • 調査では、はじめに CPU 使用率トレースを取得します。 多くの場合、CPU 使用率ツールは、パフォーマンスの調査を開始し、コードを最適化してコストを削減するのに役立ちます。
  • 問題の特定やパフォーマンスの向上に役立つ分析情報がさらに必要な場合は、他のプロファイル ツールのいずれかを使ってトレースを収集することを検討してください。 例:
    • メモリの使用量を調べます。 .NET の場合は、最初に .NET オブジェクト割り当てツールを試してください。 .NET または C++ の場合は、メモリ使用量ツールを確認できます。
    • アプリでファイル I/O を使っている場合は、ファイル I/O ツールを使います。
    • ADO.NET または Entity Framework を使っている場合は、データベース ツールを使って SQL クエリ、正確なクエリ時間などを調べてみることができます。

データ コレクションの例

この記事で示すスクリーンショットの例は、ブログのデータベースに対してクエリを実行する .NET アプリと、関連するブログ記事に基づいています。 最初に CPU 使用率トレースを調べて、コードを最適化してコンピューティング コストを削減する余地がないかを探します。 起きていることがだいたい分かったら、他のプロファイル ツールからのトレースも確認して、問題を突き止めます。

データ コレクションには、次の手順が必要です (ここでは示していません)。

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

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

まず、CPU 使用率ツールを使ってトレースを収集します。 診断データを読み込んだら、まず、最上位の分析情報とホット パスが示されている最初の .diagsession レポート ページを調べます。 ホット パスは、アプリで CPU 使用率が最も高いコード パスを示します。 これらのセクションでは、改善できるパフォーマンスの問題をすばやく特定するのに役立つヒントが提供される場合があります。

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

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

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

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

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

視覚化された呼び出しツリーと、データの別のビューを見るには、フレーム グラフ ビューに切り替えます (呼び出しツリーと同じ一覧から選びます)。 ここでも、アプリの CPU 使用率の多くは GetBlogTitleX メソッドによるものであるようです (黄色で表示)。 GetBlogTitleX ボックスの下に示されている LINQ DLL の外部呼び出しで、メソッドのすべての CPU 時間が使われています。

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

追加データを収集する

多くの場合、他のツールは、分析に役立つ追加情報を提供し、問題を特定できます。 たとえば、LINQ DLL を特定したので、まずデータベース ツールを試します。 このツールと 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"

ここでは、必要以上に多くの列値を取得していることに注意してください。

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

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

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

コードの最適化

ここで、GetBlogTitleX のソース コードを見てみましょう。 .NET オブジェクト割り当てツールで、メソッドを右クリックして、[ソース ファイルに移動] を選びます。 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 使用率のスクリーンショット。

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

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

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

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

次に、.NET オブジェクト割り当てツールの結果をもう一度確認すると、GetBlogTitleX によるオブジェクトの割り当ては 56,000 のみであり、900,000 から 95% 近く減っていることがわかります。

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

繰り返す

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

次のステップ

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