4

プロセス内のすべての .Net メソッド呼び出しをログに記録するプロファイラーを作成しようとしています。目標は、パフォーマンスを高め、ユーザーがその情報をディスクに書き込むようにトリガーするまで、最後の 5 ~ 10 分間をメモリ (固定バッファー、周期的に古い情報を上書きする) に保持することです。使用目的は、めったに再現されないパフォーマンスの問題を追跡することです。

https://github.com/appneta/SimpleCLRProfilerの SimpleCLRProfiler プロジェクトから始めました。プロファイラーは、.Net プロファイリングのICorProfilerCallback2コールバック インターフェイスを利用します。私の環境(Win 8.1、.Net 4.5、VS2012)でコンパイルして動作させることができました。ただし、Enter コールがログに記録されているのに、Leave コールが欠落している場合があることに気付きました。Console.WriteLine 呼び出しの例 (DbgView の出力を、理解するために最低限必要なものに減らしました):

Line 1481: Entering System.Console.WriteLine
Line 1483: Entering SyncTextWriter.WriteLine
Line 1485: Entering System.IO.TextWriter.WriteLine
Line 1537: Leaving SyncTextWriter.WriteLine

2 つの Entering 呼び出しには、対応する Leaveing 呼び出しがありません。プロファイリングされた .Net コードは次のようになります。

Console.WriteLine("Hello, Simple Profiler!");

関連する SimpleCLRProfiler メソッドは次のとおりです。

HRESULT CSimpleProfiler::registerGlobalCallbacks() 
{
   HRESULT hr = profilerInfo3->SetEnterLeaveFunctionHooks3WithInfo(
      (FunctionEnter3WithInfo*)MethodEntered3, 
      (FunctionEnter3WithInfo*)MethodLeft3, 
      (FunctionEnter3WithInfo*)MethodTailcall3);

   if (FAILED(hr))
      Trace_f(L"Failed to register global callbacks (%s)", _com_error(hr).ErrorMessage());

   return S_OK;
}

void CSimpleProfiler::OnEnterWithInfo(FunctionID functionId, COR_PRF_ELT_INFO eltInfo)
{
    MethodInfo info;
   HRESULT hr = info.Create(profilerInfo3, functionId);
   if (FAILED(hr)) 
      Trace_f(L"Enter() failed to create MethodInfo object (%s)", _com_error(hr).ErrorMessage());

   Trace_f(L"[%p] [%d] Entering %s.%s", functionId, GetCurrentThreadId(), info.className.c_str(), info.methodName.c_str());
}

void CSimpleProfiler::OnLeaveWithInfo(FunctionID functionId, COR_PRF_ELT_INFO eltInfo)
{
   MethodInfo info;
   HRESULT hr = info.Create(profilerInfo3, functionId);
   if (FAILED(hr)) 
      Trace_f(L"Enter() failed to create MethodInfo object (%s)", _com_error(hr).ErrorMessage());

   Trace_f(L"[%p] [%d] Leaving %s.%s", functionId, GetCurrentThreadId(), info.className.c_str(), info.methodName.c_str());
}

.Net プロファイラーがすべての終了メソッドに対して Leave 呼び出しを実行しない理由を知っている人はいますか? ちなみに、OnLeaveMethod が例外などでトレース前に予期せず終了しないことを確認しました。そうではありません。

ありがとう、クリストフ

4

1 に答える 1

4

stakx は公式の回答を提供する (そしてクレジットを得る) ために私の質問に戻ってこないようなので、私は彼のためにそれを行います: stakx がほのめかしたように、テールコールをログに記録しませんでした。実際、私はその概念さえ知らなかったので、そのフック メソッドを完全に無視していました (接続されていましたが空でした)。ここでテール コールの適切な説明を見つけました: David Broman's CLR Profiling API Blog: Enter, Leave, Tailcall Hooks Part 2: Tall tales of tail calls

上記のリンクから引用します:

テール呼び出しは、命令の実行を節約し、スタック メモリの読み取りと書き込みを節約するコンパイラの最適化です。関数が最後に別の関数を呼び出す場合 (および他の条件が有利な場合)、コンパイラは、通常の呼び出しではなく、末尾の呼び出しとしてその呼び出しを実装することを検討する場合があります。

次のコードを検討してください。

static public void Main() {
    Helper();
}

static public void Helper() {
    One();
    Three();
}

static public void Three() {
    ...
}

末尾呼び出しの最適化を行わずにメソッドThreeを呼び出すと、スタックは次のようになります。

Three
Helper
Main

末尾呼び出しの最適化を使用すると、スタックは次のようになります。

Three
Main

Threeを呼び出す前に、最適化のためにメソッドHelperは既にスタックからポップされていました。その結果、スタック上のメソッドが 1 つ少なくなり (メモリ使用量が少なくなり)、一部の実行とメモリ書き込み操作が保存されました。

于 2015-06-23T02:28:36.463 に答える