次の方法で共有


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

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

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

サンプル アプリについて

この記事のスクリーンショットは、ブログのデータベースに対してクエリを実行する .NET アプリと、関連するブログ記事に基づいています。 これは Entity Framework の例に基づいていますが、SQLite ローカル データベースに対して多数のクエリを実行するように変更されています。

調査を開始する

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

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

  • アプリをリリース ビルドに設定します。
  • パフォーマンス プロファイラーから 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 がハイライトされた呼び出しツリー ビューのスクリーンショット。

視覚化された呼び出しツリーとデータの別のビューを取得するには、GetBlogTitleX を右クリックして [View in Flame Graph] (フレーム グラフで表示) を選択します。 ここでも、アプリの CPU 使用率の多くは GetBlogTitleX メソッドによるものであるようです (黄色で表示)。 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 クエリを最適化し、このコードを考え出す方法に関するいくつかの一般的な推奨事項を見つけます。

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 パフォーマンス ツールの効果的な使用方法を学習するのに役立つ情報を提供しています。