Visual Studio 2015

Visual Studio 2015 でのデバッグ中のパフォーマンス分析

Charles Willis
Dan Taylor

多くの開発者は、アプリケーションを正しく機能させることに大半の時間を費やします。アプリケーションのパフォーマンスに注目することはほとんどありません。昔から Visual Studio にはプロファイリング ツールがありましたが、これは独立したツールで使用するには学習が必要でした。多くの開発者には、パフォーマンスの問題が発生したときに使用するためにプロファイリング ツールを学ぶ時間はありません。

今回は、Visual Studio 2015 の新しい診断ツール デバッガー ウィンドウを取り上げ、これをデバッグ ワークフローの標準手順として使用し、パフォーマンスを分析する方法を説明します。最初に、デバッガーの機能と能力の概要を示し、順を追って詳しく見ていきます。説明の過程で、ブレークポイント間やステップ間のコード セクションの時間を計測するために PerfTips (パフォーマンス ヒント) を使用する方法、CPU とメモリを監視するために診断ツール ウィンドウを使用する方法、およびメモリ使用量の増加やメモリ リークを詳しく調査するためにスナップショットを取得する方法も示します。

今回示す機能は、ほとんどのマネージ プロジェクトやネイティブ プロジェクトのデバッグに使用できます。マイクロソフトは、より多くのプロジェクトの種類やデバッグ構成向けのサポートを絶えず追加しています。現在サポート対象となる機能に関する最新情報については、診断ツール ウィンドウに関するブログ投稿 (aka.ms/diagtoolswindow、英語) を参照してください。今月号の別の記事でも、診断ツール ウィンドウ内で IntelliTrace を使用して、コードのバグの根本原因を迅速に特定する方法を紹介しています。

デバッグ中のパフォーマンス

パフォーマンスを測定する場合、完全版のプロファイリング ツールを実行する代わりに、以下のような方法を採用しているかもしれません。

  1. アプリケーションにコード (System.Diagnostics.Stopwatch など) を挿入して、さまざまなポイント間での実行時間を測定する。必要に応じてこのストップウォッチ コードを繰り返し追加してホット スポットを絞り込む。
  2. コードをステップスルーして、特定のステップで "遅いと感じる" かどうかを確認する。
  3. ポイントをランダムに選んで [すべて中断] ボタン ("一時停止" ボタン) をクリックし、どの程度実行が進んだかを考える (これを「初歩的なサンプリング」と呼ぶこともあります)。
  4. 場合によってはパフォーマンスをまったく測定しないで、コード ベース全体にわたってパフォーマンスのベスト プラクティスのセットを当てはめ、コードの過剰な最適化を行う。

このような手法は一般に正確ではなく、適切な時間の使い方でもありません。このような理由から、デバッガーにパフォーマンス ツールが用意されることになりました。その結果、普通にデバッグしているときに、アプリケーションのパフォーマンスを把握できるようになります。

[Diagnostic Tools] (診断ツール) ウィンドウ: Visual Studio 2015 でコードをデバッグしていると、これまでとは違って新しい [診断ツール] ウィンドウがあるのがわかります (図 1 参照)。この [診断ツール] では、2 つの補完的な方法で情報が表示されます。ウィンドウの上半分にはタイムラインを示すグラフが追加され、下半分のタブに詳細情報が表示されます。

Visual Studio 2015 の新しい [診断ツール] ウィンドウ
図 1 Visual Studio 2015 の新しい [診断ツール] ウィンドウ

Visual Studio 2015 の [診断ツール] ウィンドウには、[Debugger] (デバッガー) (IntelliTrace を含む)、[Memory Usage] (メモリ使用量)、[CPU Usage] (CPU 使用率) という 3 つのツールがあるのがわかります。[Select Tools] (ツールの選択) ドロップダウンをクリックして、CPU 使用率ツールとメモリ使用量ツールの有効、無効を切り替えることができます。デバッガー ツールには、Break Events (中断イベント)、Output Events (出力イベント)、および IntelliTrace Events (IntelliTrace イベント) を表示する 3 つのトラックがあります。

中断イベントの履歴とパフォーマンス ヒント: 中断イベントにより、各コード セクションの実行時間を確認できます。四角形は、アプリケーションの開始または再開時から、デバッガーによってアプリケーションが一時停止されるまでの期間を表します (図 2 参照)。

中断イベントとパフォーマンス ヒント
図 2 中断イベントとパフォーマンス ヒント

四角形の左端は、続行 (F5) 、ステップ実行 (F10F11Shift+F11)、カーソル行の前まで実行 (Ctrl+F10) を使用してアプリケーションを開始した時点を表します。四角形の右端は、ブレークポイントに到達、ステップを完了、またはすべて中断により、アプリケーションが停止した時点を表します。

最新の中断イベントの期間は、デバッガーの現在行の末尾にコードでも表示されます。これをパフォーマンス ヒントとと呼びます。これがあれば、コードから目を離すことなく絶えずパフォーマンスを確認できます。

グラフの下にある詳しい表には、中断イベントの履歴と期間、およびパフォーマンス ヒントが表形式で示されます。IntelliTrace を使用していると、表には追加のイベントが表示されます。フィルターを使用して、デバッガーに中断イベントの履歴だけを表示することもできます。

CPU とメモリの分析: ブレークポイントやステップの設定に応じて、タイムラインでは自動的に時間範囲が選択されます。ブレークポイントに達すると現在の時間範囲がリセットされ、最新の中断イベントだけが表示されます。最新の中断イベントを含むように、選択範囲を拡張することができます。中断イベントの四角形をクリックするか、タイムライン上をクリックしてドラッグすることで、自動的に選択される時間範囲を変更できます。

時間範囲を選択することで、CPU 使用率グラフとメモリ使用量グラフの範囲を相互に関連付けて、特定のコード セクションにおける CPU とメモリの特性を把握できます。アプリケーションの実行中、グラフは絶えず更新されるため、アプリケーションを操作しながら CPU とメモリの状況を監視できます。[メモリ使用量] タブに切り替えてスナップショットを取得し、メモリ使用量の詳しい情報を表示します。

IntelliTrace によるパフォーマンスの洞察: IntelliTrace (Visual Studio Community バージョンでは利用不可) により、マネージ コードのデバッグ時にパフォーマンスに関する多くの洞察を得ることができます。IntelliTrace は、[デバッガー イベント] のタイムラインに Output (出力) トラックと、IntelliTrace トラックの 2 つのトラックを追加します。これらのイベントには、出力ウィンドウに表示される情報に加えて、IntelliTrace が収集する追加のイベント (例外、ADO.NET など) も含まれます。これらのトラックのイベントは、[デバッガー イベント] の表にも表示されます。

IntelliTrace のイベントは、CPU 使用率の急増やメモリ使用量のグラフに関連付けることができます。タイム スタンプは、アプリケーションのさまざまな操作にかかった時間を示します。たとえば、Debug.WriteLine ステートメントをコードに追加し、出力イベントでタイムスタンプを使用して、あるステートメントから次のステートメントまでの実行時間を確認できます。

パフォーマンスとメモリ使用量の改善

ここまではウィンドウの機能を見てきましたが、ここからはツールの実際の使い方を詳しく見ていきます。ここでは、PhotoFilter というサンプル アプリケーションのパフォーマンス問題を解決していく手順を紹介します。このアプリケーションは、クラウドから写真をダウンロードしたり、ユーザーのローカルにあるライブラリから写真を読み込んで、画像の表示や画像のフィルター選択を可能にします。

ソース コードを手順の参考にする場合は、aka.ms/diagtoolswndsample からダウンロードしてください。コンピューターが変わればパフォーマンスも異なるため、異なる数値になることがわかります。また、実行するたびに数値がに変化することもあります。

アプリケーションの起動に時間がかかる: PhotoFilter アプリケーションのデバッグを始めると、アプリケーションの起動と画像の読み込みに長い時間がかかるのがわかります。これは明らかな問題点です。

アプリケーション機能の問題点をデバッグするときは、多くの場合、仮説を立て、その仮説に基づいてデバッグを始めます。今回は、画像の読み込みが遅いという仮説を立て、ブレークポイントを設定するのに適した場所を探し、この仮説を検証します。LoadImages メソッドが、ブレークポイントの設定に適した場所です。

LoadImages 関数の最初と最後にブレークポイントを設定し (図 3 のコード参照)、デバッグを始めます (F5)。コードが最初のブレークポイントに達したら、再度続行ボタン (F5) を押して、2 つ目のブレークポイントまで実行します。これにより、[デバッガー イベント] のタイムラインには、2 つの中断イベントが表示されるようになります。

LoadImages メソッド

LoadImages メソッド
図 3 LoadImages メソッド

最初のステップには、最初のブレークポイントに達するまでは、たった 274 ミリ秒しかかかっていないことが示されています。2 つ目のステップには、2 つ目のブレークポイントに達するまでの LoadImages 関数のコードの実行に 10,476 ミリ秒かかっていることが示されています。コードでは、同じ経過時間を表示するパフォーマンス ヒントも確認できます。これにより、問題を生じている範囲が LoadImages 関数に絞り込まれます。

より多くの情報と行にかかった時間を確認するには、デバッグを再開し、再度最初のブレークポイントに達するのを待ちます。今度は、LoadImages メソッドの各コード行をステップ スルーして、最も時間がかかっている行を特定します。パフォーマンス ヒントとデバッグ中断イベントの期間から、GetImagesFromCloud に 7,290 ミリ秒、LoadImagesFromDisk に 736 ミリ秒、LINQ クエリに 1,322 ミリ秒かかり、残りは 50 ミリ秒以下で完了していることがわかります。

すべての行の実行タイミングを図 4 に示します。表示される行番号は中断イベントが終わる行を表すため、行 52 を示している場合は、行 51 をステップ オーバーするのにかかった時間を表します。ここからは、時間がかかっている GetImagesFromCloud メソッドを詳しく見ていきます。

各ステップの実行時間を示すデバッガー イベントの表
図 4 各ステップの実行時間を示すデバッガー イベントの表

GetImagesFromCloud メソッドは、ロジックとしては独立した 2 つの操作を実行します (図 5 参照)。1 つの操作は、サーバーからの写真のリストのダウンロードです。もう 1 つは、各画像のサムネイルのダウンロードです。こちらは 1 度に 1 枚ずつ同期をとってダウンロードします。先ほどのブレークポイントの設定を解除し、新しいブレークポイントを次の 3 行に設定して、2 つの操作の時間を測定します。

line 63: HttpClient client = new HttpClient();
line 73: foreach (var image in pictureList)
line 79: }

GetImagesFromCloud メソッドImproved Code
図 5 GetImagesFromCloud メソッド (上) と改善後のコード (下)

デバッグ プロセスを再開し、最初のブレークポイントに達するのを待ちます。最初のブレークポイントに到達後、実行を再開 (F5) し、2 つ目のブレークポイントに達するのを待ちます。この手順で、クラウドから写真のリストを取得します。次に、2 つ目のブレークポイントから実行を再開し、クラウドからのサムネイルのダウンロードを計測します。パフォーマンス ヒントと中断イベントから、写真リストの取得には 565 ミリ秒、サムネイルをダウンロードには 6,426 ミリ秒かかったことがわかります。パフォーマンスのボトルネックは、サムネイルのダウンロードです。

メソッドが写真リストを取得する際の CPU 使用率のグラフ (図 6) を見ると、CPU の使用率が比較的高いことがわかります。このグラフはサムネイルのダウンロード中はほとんど横ばいの状態を示しており、このプロセスではネットワーク I/O の待機に時間がかかっていることがわかります。

ネットワーク I/O の遅延を示す CPU 使用率グラフ
図 6 ネットワーク I/O の遅延を示す CPU 使用率グラフ

クライアントとサーバー間のラウンドトリップの待機時間を最小限に抑えるため、すべてのサムネイルのダウンロード操作を 1 度に開始して .NET System.Tasks の完了を待機することで、操作の完了を待つようにします。図 5 のコードの 73 ~ 79 行目を、次のコードに置き換えます。

// Download thumbnails
var downloadTasks = new List<Task>();
foreach (var image in pictureList)
{
  string fileName = image.Thumbnail;
  string imageUrl = ServerUrl + "/Images/" + fileName;
  downloadTasks.Add(DownloadImageAsync(new Uri(imageUrl), folder, fileName));
}
await Task.WhenAll(downloadTasks);

この新しいバージョンで時間を計測すると、実行に 2,424 ミリ秒しかかからなくなったことを確認できます。つまり、約 4 秒の向上です。

メモリ使用量の増加とメモリ リークのデバッグ: 起動に時間がかかる原因の調査中にメモリ使用量のグラフを見ると、アプリ起動時にメモリ使用量が急増していることがわかります。サムネイルのリストは仮想リストなので、フル サイズの画像は一度に 1 枚しか表示されません。仮想リストを使用するメリットの 1 つは、画面に表示するコンテンツだけを読み込み、1 度に多数のサムネイルをメモリに読み込むことは想定しません。

この問題の根本原因を突き止めるには、まず、コードのどこでメモリ使用量の増加が発生しているかを見つけなければなりません。そこで、メモリ増加の前後でスナップショットを取得します。2 つのスナップショットを比較すると、オブジェクトの型がメモリ増加の主要因になっていることがわかります。

メモリ使用量のグラフは、アプリケーションがメモリを使用する方法を大まかに示すものです。アプリケーション用に "Private Bytes" というパフォーマンス カウンターがあります。"Private Bytes" (プライベート バイト数) とは、プロセスに割り当てられるメモリ総量の測定値です。これには、他のプロセスと共有するメモリの量は含まれません。他のプロセスと共有するメモリには、マネージ ヒープ、ネイティブ ヒープ、スレッド スタック、その他のメモリ (読み込んだ .dll ファイルのプライベート セクションなど) があります。

新しいアプリケーションの開発中や、既存のアプリケーションの問題の診断中に、メモリ使用量のグラフに予期しないメモリ使用量の増加が示される場合は、想定どおりに動作していないコードがあることを示す最初の兆候になります。グラフを確認し、ブレークポイントやステップ実行などデバッガー機能を使用して、対象とするコード パスを絞り込むことができます。前述の図 4 の [デバッガー イベント] タブに示されている行番号と期間から、想定外のメモリ使用量の増加を招いた行は 52 行目 (LoadImagesFromDisk メソッドの呼び出し) であることがわかります。想定外のメモリ使用量を特定する次の手順は、多くの場合、スナップショットを取得することです。[メモリ使用量] タブの [Take Snapshot] (スナップショットの取得) をクリックして、ヒープのスナップショットを作成します。ブレークポイントでスナップショットを取得することも、アプリケーションの実行中にスナップショットを取得することもできます。

どのコード行がメモリ使用量の急増を引き起こしているかがわかると、最初のスナップショットをどこで取得すべきかがわかります。LoadImagesFromDisk メソッドにブレークポイントを設定し、コードがブレークポイントに到達したときにスナップショットを取得します。このスナップショットがベースラインになります。

次に、LoadImagesFromDisk メソッドをステップ オーバーし、もう 1 つのスナップショットを取得します。ここで、取得した 2 つのスナップショットを比較し、ステップ オーバーした関数の呼び出しによってどのマネージ型がヒープに追加されたかを確認します。グラフには、調査中のメモリ使用量の急増が再度示されます (図 7 参照)。グラフ上で急増している場所をポイントすると、メモリが 47.4 MB 使用されていることも確認できます。修正よって大きな効果が得られるかどうかを確認できるように、この数値を記録しておくとよいでしょう。

メモリ使用量の顕著な急増を示す画面
図 7 メモリ使用量の顕著な急増を示す画面

詳細ビューには、各スナップショットの簡単な概要が表示されます。この概要には、スナップショットのシーケンス番号、スナップショット取得時の実行時間 (秒)、ヒープのサイズ、ヒープ上に存続するオブジェクトの数などが示されます。2 つ目のスナップショットには、前のスナップショットと比較したサイズの変化やオブジェクト数の変化も示されます。

スナップショットを取得するプロセスでは、ヒープにまだ存在しているオブジェクトだけが列挙されます。つまり、オブジェクトがガベージ コレクションの対象になっている場合は、スナップショットに含まれません。そのため、ガベージ コレクションが最後に実行されたタイミングを考える必要はありません。すべてのスナップショットのデータは、ガベージ コレクション実行直後の状態と同じです。

スナップショットの概要に示されるヒープのサイズは、メモリ使用量グラフに表示されるプライベート バイト数よりも少なくなります。プライベート バイト数の概要には、プロセスによって確保されたすべての型のメモリが示されますが、スナップショットにはマネージ ヒープ上に存在しているオブジェクトのサイズだけが示されます。メモリ使用量のグラフでメモリ使用量の急増が確認されても、マネージ ヒープの増加が原因でなければ、増加はメモリの他の場所で発生しています。

スナップショットの概要から、[Heap View] (ヒープの表示) を開き、型別にヒープのコンテンツを調査します。[Objects (Diff)] (オブジェクト数 (違い)) 列にある 2 つ目のスナップショットのリンクをクリックして、ヒープの表示を新しいタブで開きます。リンクをクリックすると、前のスナップショット以降に作成された新しいオブジェクト数を基準に、ヒープ表示の型が並べ替えられます。その結果、目的の型が表の上部に表示されます。

[Heap View Snapshot] (ヒープ表示のスナップショット) (図 8 参照) には、ウィンドウの上部に [Object Type] (オブジェクトの種類) 別の表と、ウィンドウの下部に [References Graph] (参照グラフ) という 2 つのメイン セクションがあります。オブジェクトの種類別の表には、スナップショット取得時の各オブジェクトの型名、数、およびサイズが示されます。

Diff モードでのヒープ表示のスナップショット
図 8 Diff モードでのヒープ表示のスナップショット

ヒープ表示の型のいくつかは、フレームワークの型です。サンプル コードを単純に (既定の状態で) 有効にすると、コード内での参照型か、コード内の型によって参照される型のいずれかになります。このビューを使用すると、サンプル コードの型 (PhotoFilter.ImageItem) を表の上部付近で確認できます。

図 8 では、前のスナップショット以降、137 個の新しい ImageItem オブジェクトが作成されたことが [Count Diff] (数の相違) 列からわかります。上位 5 個の新しいオブジェクト型はすべて、同じ数の新しいオブジェクトを保持しているため、関連がありそうです。

[参照グラフ] という 2 つ目のウィンドウを見てみましょう。ガベージ コレクターによって特定の型がクリーン アップされることを想定していても、オブジェクトの種類別の表にまだ表示されている場合は、[Paths to Root] (ルートへのパス) を使用して何が参照を保持しているかを特定します。[ルートへのパス] は、[参照グラフ] に表示される 2 つのビューの 1 つです。[ルートへのパス] とは、選択された型をルートとする型の完全なグラフを表示するボトム アップ型のツリーです。存在する別のオブジェクトが参照を保持している場合は、そのオブジェクトがルートになります。不必要にルートになっているオブジェクトは、多くの場合、マネージ コードでのメモリ リークの原因となります。

[Referenced Types] (参照する型) というもう 1 つのビューはこの逆の状態を示します。オブジェクトの種類別の表で選択した型について、選択した型が他のどの型を参照しているかがこのビューに示されます。この情報は、選択した型のオブジェクトが想定よりも多くのメモリを保持している理由を判断する際に役立ちます。型が想定よりも多くのメモリを使用していても、有用性を失っていないため、この情報が調査の役立ちます。

オブジェクトの種類別の表で [PhotoFilter.ImageItem] 行を選択します。[参照グラフ] が更新され、ImageItem のグラフが表示されます。[参照する型] ビューでは、ImageItem のオブジェクトが合計約 280 個の String オブジェクトを保持し、StorageFile、StorageItemThumbnail、BitmapImage という 3 つのフレームワーク型をそれぞれ約 140 個保持していることがわかります。

[Total Size (Bytes)] (合計サイズ (バイト数)) を確認すると、ImageItem のオブジェクトが保持するメモリ量増加の主要因は String オブジェクトにありそうなことがわかります。[合計サイズの違い (バイト数)] に注目することにも意味はありますが、数値だけでは根本原因を突き止めることができません。BitmapImage のようなフレームワーク型が占めるメモリ量は、マネージ ヒープで保持される合計メモリ量のうちほんのわずかです。BitmapImage インスタンスの数がより手がかりになります。前述のように、PhotoFilter のサムネイルのリストは仮想リストなので、必要に応じて画像を読み込み、完了後にガベージ コレクションの対象にする必要があります。しかし、すべてのサムネイルが事前に読み込まれているように見えます。BitmapImage のオブジェクトをヒントに、現在わかっていることと組み合わせて、ここに集中して調査を続けます。

[参照グラフ] の [PhotoFilter.ImageItem] を右クリックし、[Go To Definition] (定義へ移動) をクリックして、エディターで ImageItem のソース ファイルを開きます。ImageItem は、BitmapImage 型のメンバー フィールド m_photo を定義しています (図 9 参照)。

m_photo メンバー フィールドを参照するコード
図 9 m_photo メンバー フィールドを参照するコード

m_photo を参照する最初のコード パスは、UI のサムネイルのリスト ビューにデータバインドされる Photo プロパティの get メソッドです。BitmapImage は要求時に読み込まれ (ネイティブ ヒープでデコードされ) ているように見えます。

m_photo を参照する 2 つ目のコード パスは、LoadImageFromDisk 関数です。この項目は、アプリケーションの起動パスです。LoadImageFromDisk 関数は、アプリケーションの起動時に、表示予定の各画像用に呼び出されます。これによって、事実上、すべての BitmapImage オブジェクトが事前に読み込まれることになります。この動作が仮想リスト ビューに対して行われ、リスト ビューに画像のサムネイルを表示しているかどうかに関係なく、すべてのメモリが確保されることになります。この事前読み込みのアルゴリズムでは適切にスケーリングされません。写真ライブラリの画像が増えるにつれて、起動時のメモリ コストが高まります。BitmapImage オブジェクトを要求時に読み込む方が、スケーラビリティの高いソリューションです。

デバッガーを停止後、LoadImageFromDisk にある、BitmapImage インスタンスを読み込む 81 行目と 82 行目をコメント アウトします。アプリケーションの機能を停止することなくメモリ パフォーマンスの問題を解決できたかどうかを確認するために、同じテストを繰り返します。

F5 キーを押すと、メモリ使用量のグラフの合計メモリ使用量が 26.7MB になったことを確認できます (図 10 参照)。LoadImagesFromDisk の呼び出しの前後で別のスナップショットを取得し、比較します。まだ 137 個の ImageItem オブジェクトがあることを確認できますが、BitmapImages は確認できません (図 11 参照)。アプリケーションを再度起動すると、要求時に BitmapImages が読み込まれるようになります。

参照の問題を解決した後のメモリ使用量のグラフ
図 10 参照の問題を解決した後のメモリ使用量のグラフ

メモリの問題を解決した後の参照グラフ
図 11 メモリの問題を解決した後の参照グラフ

前述のように、このデバッガー統合ツールでは、ネイティブ ヒープのスナップショットの取得や、マネージ ヒープとネイティブ ヒープの両方のスナップショットの取得もサポートされます。プロファイリングの対象となるヒープは、使用するデバッガーに基づきます。

  • マネージ専用のデバッガーは、マネージ ヒープのスナップショットだけを取得します。
  • ネイティブ専用のデバッガー (ネイティブ プロジェクトの既定) は、ネイティブ ヒープのスナップショットだけを取得します。
  • 混合モードのデバッガーは、マネージ ヒープとネイティブ ヒープの両方のスナップショットを取得します。

プロジェクトのプロパティの [デバッグ] ページで、この設定を変更できます。

デバッグなしでツールを実行するタイミング

デバッガーでパフォーマンスを測定する場合、新たなオーバーヘッドが生じることに留意しておくことが重要です。オーバーヘッドが生じる原因は、アプリケーションをデバッグ ビルドで実行している点にあります。ユーザーにリリースするアプリケーションはリリース ビルドです。

デバッグ ビルドの場合、コンパイラは実行可能ファイルを、構造や動作の点でオリジナルのソース コードにできるだけ近づけようとします。デバッグ中は、すべてが想定どおりに動作する必要があります。一方、リリース ビルドでは、デバッグ時のエクスペリエンスを取り除き、パフォーマンスが向上するようにコードの最適化を試みます。たとえば、関数や定数変数をインライン化し、不必要な変数やコード パスを削除し、デバッガーでは読み取れなくなる方法で変数情報を格納します。

これはすべて、CPU を集中的に使用するコードでは、デバッグ ビルドの方がかなり低速になる可能性があることを意味しています。CPU を集中的に使用しないディスク I/O やネットワーク呼び出しを伴う操作では、どちらのビルドでも同じ時間がかかります。通常、メモリ内の動作には大きな違いはありません。つまり、どちらの場合でも、リークしたメモリは同じようにリークし、非効率なメモリの使用方法はメモリ使用量の増加につながります。

他にも、デバッガーをターゲット アプリケーションにアタッチした場合に追加されるオーバーヘッドについても考慮する必要があります。デバッガーは、モジュールの読み込みや例外などのイベントをインターセプトします。また、デバッガーは、ブレークポイントやステップ実行を設定するために必要な作業も行います。Visual Studio は、この種のオーバーヘッドをパフォーマンス ツールから除外するためにベストを尽くしていますが、それでも少量のオーバーヘッドがあります。

アプリケーションのリリース ビルドに問題がある場合、ほぼ必ずデバッグ ビルドでも問題を再現できますが、その逆は必ずしも成り立ちません。そのため、開発中にパフォーマンスの問題を積極的に見つけられように、デバッガー統合ツールが用意されています。デバッグ ビルドで問題が見つかった場合、リリース ビルドに移動して、問題がリリース ビルドにも影響するかどうかを確認します。ただし、デバッグ ビルドで先に問題を解決することに決めてもかまいません。たとえば、パフォーマンスに関する予防策になる (問題を解決しておくことで、後からパフォーマンスの問題に直面する機会が少なくなる) と判断できる場合、CPU を集中的に使用しないケースでの問題 (ディスクやネットワーク I/O の問題) であると判断できる場合、開発中にアプリケーションの速度を上げるためにデバッグ ビルドで速度の向上を図る場合などです。

パフォーマンスの問題がリリース ビルドで報告されたときは、問題を再現して問題を解決するようにします。これを行う最善の方法は、ビルドをリリース モードに切り替え、報告された問題に正確に一致する環境でデバッガーを使わずにツールを実行することです。

操作期間を測定する場合、デバッガー統合ツールの精度はオーバーヘッドの関係では数十ミリ秒単位になります。高い精度を求める場合は、デバッガーを使わずにツールを実行することをお勧めします。

まとめ

Visual Studio 2015 RC をダウンロードすることで、Visual Studio 2015 の新しい診断ツール デバッガー ウィンドウをお試しいただけます。この新しい統合デバッグ ツールを使用すると、アプリケーションのデバッグ中にパフォーマンスを向上させることができます。


Dan Taylor は、Visual Studio Diagnostics チームのプログラム マネージャーを務めています。ここ 2 年間はプロファイリングと診断のツールに携わっています。Visual Studio Diagnostics チームに参加する前は、.NET Framework チームのプログラム マネージャーを務め、.NET Framework to CLR のパフォーマンス向上に大きく貢献しました。

Charles Willis は、昨年マイクロソフトに入社して以来、Visual Studio Diagnostics チームのプログラム マネージャーを務めています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Andrew Hall、Daniel Moth、および Angelos Petropoulos に心より感謝いたします。
Angelos Petropoulos は、Visual Studio チームのシニア プログラム マネージャーを務めています。オブジェクト指向ソフトウェア エンジニアリングの修士号を取得後は、英国で IT コンサルタントを務めました。米国に移った後、Visual Studio の診断ツール チームに参加し、現在は IntelliTrace のプロジェクト マネージャーを務めています。

Daniel Moth は、マイクロソフトの Visual Studio チームに所属するプリンシパル プログラム マネージャーです。