プロファイラーは、別のアプリケーションの実行を監視するツールです。 共通言語ランタイム (CLR) プロファイラーは、プロファイリング API を使用して CLR との間でメッセージを受信し、CLR にメッセージを送信する関数で構成されるダイナミック リンク ライブラリ (DLL) です。 プロファイラー DLL は実行時に CLR によって読み込まれます。
従来のプロファイリング ツールでは、アプリケーションの実行を測定することに重点を置きます。 つまり、各関数に費やされた時間または時間の経過に伴うアプリケーションのメモリ使用量を測定します。 プロファイリング API は、コード カバレッジ ユーティリティや高度なデバッグ支援など、より広範なクラスの診断ツールを対象とします。 これらの用途はすべて、本質的に診断です。 プロファイリング API は、測定だけでなく、アプリケーションの実行も監視します。 このため、プロファイリング API はアプリケーション自体で使用しないでください。また、アプリケーションの実行がプロファイラーに依存 (またはプロファイラーの影響を受ける) ことはありません。
CLR アプリケーションのプロファイリングには、従来のコンパイル済みコンピューター コードのプロファイリングよりも多くのサポートが必要です。 これは、CLR には、アプリケーション ドメイン、ガベージ コレクション、マネージド例外処理、コードの Just-In-Time (JIT) コンパイル (共通の中間言語の変換、または CIL、コードをネイティブ コンピューター コードに変換する) などの概念が導入されているためです。 従来のプロファイリング メカニズムでは、これらの機能に関する有用な情報を特定したり提供したりすることはできません。 プロファイリング API は、この不足している情報を効率的に提供し、CLR とプロファイリングされたアプリケーションのパフォーマンスへの影響を最小限に抑えます。
実行時の JIT コンパイルは、プロファイリングに適した機会を提供します。 プロファイリング API を使用すると、プロファイラーは、JIT コンパイル前にルーチンのメモリ内 CIL コード ストリームを変更できます。 このように、プロファイラーは、より詳細な調査を必要とする特定のルーチンにインストルメンテーション コードを動的に追加できます。 この方法は従来のシナリオでは可能ですが、プロファイリング API を使用して CLR に実装する方がはるかに簡単です。
プロファイル API
通常、プロファイル API は、マネージド アプリケーションの実行を監視するプログラムである コード プロファイラーを記述するために使用されます。
プロファイリング API はプロファイラー DLL によって使用され、プロファイリング対象のアプリケーションと同じプロセスに読み込まれます。 プロファイラー DLL はコールバック インターフェイス (.NET Framework バージョン 1.0 および 1.1 の ICorProfilerCallback 、バージョン 2.0 以降の ICorProfilerCallback2 ) を実装します。 CLR は、そのインターフェイス内のメソッドを呼び出して、プロファイルされたプロセス内のイベントをプロファイラーに通知します。 プロファイラーは、ICorProfilerInfo インターフェイスと ICorProfilerInfo2 インターフェイスのメソッドを使用してランタイムにコールバックし、プロファイリングされたアプリケーションの状態に関する情報を取得できます。
注
プロファイラー ソリューションのデータ収集部分のみが、プロファイリング対象アプリケーションと同じプロセスで実行されている必要があります。 すべてのユーザー インターフェイスとデータ分析は、別のプロセスで実行する必要があります。
次の図は、プロファイラー DLL がプロファイリング対象のアプリケーションと CLR と対話する方法を示しています。
通知インターフェイス
ICorProfilerCallback と ICorProfilerCallback2 は通知インターフェイスと見なすことができます。 これらのインターフェイスは、 ClassLoadStarted、 ClassLoadFinished、 JITCompilationStarted などのメソッドで構成されます。 CLR は、クラスの読み込みまたはアンロード、関数のコンパイルなどのたびに、プロファイラーの ICorProfilerCallback または ICorProfilerCallback2 インターフェイスで対応するメソッドを呼び出します。
たとえば、プロファイラーは、FunctionEnter2 と FunctionLeave2 の 2 つの通知関数を使用してコードのパフォーマンスを測定できます。 各通知にタイムスタンプを付け、結果を蓄積し、アプリケーションの実行中に CPU またはウォール クロック時間が最も多く消費された関数を示すリストを出力するだけです。
情報取得インターフェイス
プロファイリングに関連するその他の主要なインターフェイスは 、ICorProfilerInfo と ICorProfilerInfo2 です。 プロファイラーは、必要に応じてこれらのインターフェイスを呼び出して、分析に役立つ詳細情報を取得します。 たとえば、CLR が FunctionEnter2 関数を呼び出すたびに、関数識別子が提供されます。 プロファイラーは、 ICorProfilerInfo2::GetFunctionInfo2 メソッドを呼び出して関数の親クラス、その名前などを検出することで、その関数に関する詳細情報を取得できます。
サポートされている機能
プロファイリング API は、共通言語ランタイムで発生するさまざまなイベントとアクションに関する情報を提供します。 この情報を使用して、プロセスの内部動作を監視し、.NET Framework アプリケーションのパフォーマンスを分析できます。
プロファイリング API は、CLR で発生する次のアクションとイベントに関する情報を取得します。
CLR のスタートアップ イベントとシャットダウン イベント。
アプリケーション ドメインの作成イベントとシャットダウン イベント。
アセンブリの読み込みとアンロードイベント。
モジュールの読み込みとアンロードイベント。
COM vtable の作成イベントと破棄イベント。
Just-In-Time (JIT) コンパイルイベントとコード ピッチ ング イベント。
クラスの読み込みイベントとアンロード イベント。
スレッドの作成イベントと破棄イベント。
関数の入退出イベント。
例外。
マネージド コードとアンマネージド コードの実行の間の遷移。
異なるランタイム コンテキスト間の遷移。
ランタイムの中断に関する情報。
ランタイム メモリ ヒープとガベージ コレクション アクティビティに関する情報。
プロファイリング API は、任意の (管理されていない) COM 互換言語から呼び出すことができます。
API は、CPU とメモリの消費量に関して効率的です。 プロファイリングには、誤解を招く結果を引き起こすのに十分な大きさのプロファイル対象アプリケーションに対する変更は含まれません。
プロファイリング API は、サンプリング プロファイラーと非サンプリング プロファイラーの両方に役立ちます。 サンプリング プロファイラーは、通常のクロック ティック (たとえば、5 ミリ秒間隔) でプロファイルを検査します。 非サンプリング プロファイラーには、イベントを発生させるスレッドと同期的にイベントが通知されます。
サポートされていない機能
プロファイリング API では、次の機能はサポートされていません。
アンマネージド コード。これは、従来の Win32 メソッドを使用してプロファイリングする必要があります。 ただし、CLR プロファイラーには、マネージド コードとアンマネージド コードの境界を決定する遷移イベントが含まれています。
アスペクト指向プログラミングなどの目的で独自のコードを変更する自己変更アプリケーション。
プロファイリング API ではこの情報が提供されないため、境界チェック。 CLR は、すべてのマネージド コードの境界チェックに対して組み込みサポートを提供します。
リモート プロファイリング。これは次の理由でサポートされていません。
リモート プロファイリングにより、実行時間が延長されます。 プロファイリング インターフェイスを使用する場合は、プロファイリングの結果が過度に影響を受けないように、実行時間を最小限に抑える必要があります。 これは、実行パフォーマンスが監視されている場合に特に当てはまります。 ただし、リモート プロファイリングは、プロファイリング インターフェイスを使用してメモリ使用量を監視したり、スタック フレームやオブジェクトなどのランタイム情報を取得したりするときに制限されません。
CLR コード プロファイラーは、プロファイリングされたアプリケーションが実行されているローカル コンピューター上のランタイムに 1 つ以上のコールバック インターフェイスを登録する必要があります。 これにより、リモート コード プロファイラーを作成する機能が制限されます。
通知スレッド
ほとんどの場合、イベントを生成するスレッドも通知を実行します。 このような通知 ( FunctionEnter や FunctionLeave など) では、明示的な ThreadIDを指定する必要はありません。 また、プロファイラーは、影響を受けるスレッドの ThreadID に基づいて、グローバル ストレージ内の分析ブロックのインデックスを作成する代わりに、スレッド ローカル ストレージを使用してその分析ブロックを格納および更新することを決定する場合があります。
これらのコールバックはシリアル化されないことに注意してください。 ユーザーは、スレッド セーフなデータ構造を作成し、必要に応じてプロファイラー コードをロックして、複数のスレッドからの並列アクセスを防ぐことで、コードを保護する必要があります。 そのため、場合によっては、通常とは異なる一連のコールバックを受け取ることができます。 たとえば、マネージド アプリケーションが、同じコードを実行している 2 つのスレッドを生成しているとします。 この場合、ICorProfilerCallback::JITCompilationFinished コールバックを受信する前に、あるスレッドから関数の ICorProfilerCallback::JITCompilationStarted イベントを受け取り、もう一方のスレッドからFunctionEnterコールバックを受け取ることができます。 この場合、ユーザーは、まだ完全に Just-In-Time (JIT) コンパイルされていない可能性がある関数の FunctionEnter コールバックを受け取ります。
セキュリティ
プロファイラー DLL は、共通言語ランタイム実行エンジンの一部として実行されるアンマネージ DLL です。 その結果、プロファイラー DLL 内のコードは、マネージド コード アクセス セキュリティの制限の対象になりません。 プロファイラー DLL の唯一の制限は、プロファイリングされたアプリケーションを実行しているユーザーに対してオペレーティング システムによって課せられる制限です。
プロファイラーの作成者は、セキュリティ関連の問題を回避するために適切な予防措置を講じる必要があります。 たとえば、インストール時に、悪意のあるユーザーが変更できないように、プロファイラー DLL をアクセス制御リスト (ACL) に追加する必要があります。
コード プロファイラーでのマネージド コードとアンマネージド コードの組み合わせ
プロファイラーが正しく記述されていないと、それ自体への循環参照が発生し、予期しない動作が発生する可能性があります。
CLR プロファイリング API のレビューでは、COM 相互運用または間接呼び出しを介して相互に呼び出すマネージド コンポーネントとアンマネージド コンポーネントを含むプロファイラーを記述できる印象が生じる場合があります。
これは設計の観点からは可能ですが、プロファイリング API はマネージド コンポーネントをサポートしていません。 CLR プロファイラーは完全にアンマネージドである必要があります。 CLR プロファイラーでマネージド コードとアンマネージド コードを組み合わせようとすると、アクセス違反、プログラムエラー、またはデッドロックが発生する可能性があります。 プロファイラーのマネージド コンポーネントは、イベントをアンマネージド コンポーネントに呼び出し、その後マネージド コンポーネントを再度呼び出し、循環参照を生成します。
CLR プロファイラーがマネージド コードを安全に呼び出すことができる唯一の場所は、メソッドの共通中間言語 (CIL) 本文にあります。 CIL 本文を変更する場合は、 ICorProfilerCallback4 インターフェイスで JIT 再コンパイル メソッドを使用することをお勧めします。
以前のインストルメンテーション メソッドを使用して CIL を変更することもできます。 関数の Just-In-Time (JIT) コンパイルが完了する前に、プロファイラーはメソッドの CIL 本文にマネージド呼び出しを挿入し、JIT コンパイルできます ( ICorProfilerInfo::GetILFunctionBody メソッドを参照)。 この手法は、マネージド コードの選択的なインストルメンテーションや、JIT に関する統計とパフォーマンス データの収集に正常に使用できます。
または、コード プロファイラーは、アンマネージ コードを呼び出すすべてのマネージド関数の CIL 本文にネイティブ フックを挿入できます。 この手法は、インストルメンテーションとカバレッジに使用できます。 たとえば、コード プロファイラーでは、各 CIL ブロックの後にインストルメンテーション フックを挿入して、ブロックが確実に実行されるようにすることができます。 メソッドの CIL 本体の変更は非常に繊細な操作であり、考慮すべき多くの要因があります。
アンマネージド コードのプロファイリング
共通言語ランタイム (CLR) プロファイリング API では、アンマネージド コードのプロファイリングを最小限に抑えることができます。 次の機能が提供されています。
スタック チェーンの列挙。 この機能により、コード プロファイラーはマネージド コードとアンマネージド コードの間の境界を決定できます。
スタック チェーンがマネージド コードとネイティブ コードのどちらに対応しているかを判断します。
.NET Framework バージョン 1.0 および 1.1 では、これらのメソッドは CLR デバッグ API のインプロセス サブセットを通じて使用できます。 これらは CorDebug.idl ファイルで定義されています。
.NET Framework 2.0 以降では、この機能に ICorProfilerInfo2::D oStackSnapshot メソッドを使用できます。
COM の使用
プロファイリング インターフェイスは COM インターフェイスとして定義されていますが、共通言語ランタイム (CLR) は実際には COM を初期化してこれらのインターフェイスを使用しません。 その理由は、マネージド アプリケーションが目的のスレッド モデルを指定する前に 、CoInitialize 関数を使用してスレッド モデルを設定する必要がないようにするためです。 同様に、プロファイラー自体は、プロファイリング対象のアプリケーションと互換性のないスレッド モデルを選択し、アプリケーションが失敗する可能性があるため、 CoInitializeを呼び出さないでください。
呼び出し履歴
プロファイル API には、呼び出し履歴を取得する方法として、呼び出し履歴のスパース収集を可能にするスタック スナップショット メソッドと、呼び出し履歴を瞬時に追跡するシャドウ スタック メソッドの 2 つの方法が用意されています。
スタック スナップショット
スタック スナップショットは、一瞬でスレッドのスタックのトレースです。 プロファイル API はスタック上のマネージド関数のトレースをサポートしますが、アンマネージ関数のトレースはプロファイラー独自のスタック ウォーカーに残します。
マネージド スタックをウォークするようにプロファイラーをプログラミングする方法の詳細については、このドキュメント セットの ICorProfilerInfo2::D oStackSnapshot メソッドと、 .NET Framework 2.0 の Profiler Stack Walking: Basics and Beyond を参照してください。
シャドウ スタック
スナップショット方法を頻繁に使用すると、パフォーマンスの問題が迅速に発生する可能性があります。 スタック トレースを頻繁に取得する場合は、代わりに FunctionEnter2、FunctionLeave2、FunctionTailcall2、および ICorProfilerCallback2 例外コールバックを使用して、プロファイラーでシャドウ スタックを構築する必要があります。 シャドウ スタックは常に最新であり、スタック スナップショットが必要な場合は常にストレージにすばやくコピーできます。
シャドウ スタックは、関数の引数、戻り値、およびジェネリック インスタンス化に関する情報を取得できます。 この情報はシャドウ スタック経由でのみ使用でき、制御が関数に渡されるときに取得できます。 ただし、この情報は、関数の実行中に後で使用できない場合があります。
コールバックとスタックの深さ
プロファイラー コールバックは、スタックが非常に制約された状況で発行される場合があり、プロファイラー コールバックのスタック オーバーフローにより、すぐにプロセスが終了します。 プロファイラーでは、コールバックに応答するために、できるだけ少ないスタックを使用する必要があります。 プロファイラーがスタック オーバーフローに対して堅牢なプロセスに対して使用することを目的としている場合は、プロファイラー自体もスタック オーバーフローのトリガーを回避する必要があります。
関連トピック
| Title | Description |
|---|---|
| プロファイリング環境の設定 | プロファイラーの初期化、イベント通知の設定、Windows サービスのプロファイリングを行う方法について説明します。 |
| プロファイル インターフェイス | プロファイリング API が使用するアンマネージド インターフェイスについて説明します。 |
| グローバル静的関数のプロファイリング | プロファイリング API で使用されるアンマネージ グローバル静的関数について説明します。 |
| プロファイリング列挙 | プロファイリング API が使用するアンマネージ列挙体について説明します。 |
| プロファイル構造 | プロファイリング API で使用されるアンマネージ構造体について説明します。 |
.NET