Delphi Sampling Profilerを使用して、アプリケーションの一部をプロファイリングしました。ほとんどの人と同じように、私はほとんどの時間を室内で過ごしていntdll.dll
ます。
注:時間を無視する オプションをオンにし、
Application.Idle
から呼び出しますSystem.pas
。ntdll
アプリケーションがアイドル状態であるため、内部にはありません。
複数回実行した後、ほとんどの時間は 内ntdll.dll
で費やされているように見えますが、奇妙なことに呼び出し元は次のようになります。
呼び出し元は、仮想ツリービューからのものです:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
注:
ntdll.dll
アプリケーションがアイドル状態であるため、アプリケーションは内部にありませんApplication.Idle
。
私を混乱させているのは、この行自体(つまり、PrepareCell内ntdll
の何かではない) が呼び出し元であるということです。さらに紛らわしいのは、次のことです。
- だけでなく、それは内部の何かではありません
PrepareCell()
- 呼び出し元であるセットアップ(スタック変数のポップ、暗黙的な例外フレームのセットアップなど)でさえありません。これらは、 PrepareCell 内
PrepareCell
のホットスポットとしてプロファイラーに表示されます。begin
VirtualTrees.pas:
procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
begin
...
end;
だから私はこの行がどのように理解しようとしています:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
を呼び出してntdll.dll
います。
唯一の他の方法は、次の 3 つのパラメーターです。
PaintInfo
Window.Left
NodeBitmap.Width
おそらくそれらの 1 つは、 を呼び出す関数またはプロパティ ゲッターntdll
です。そのため、行にブレークポイントを設定し、実行時に CPU ウィンドウを確認します。
犯人である可能性のある行があります:
call dword ptr [edx+$2c]
しかし、私がそのジャンプをたどると、それは終わらないntdll.dll
が、TBitmap.GetWidth
:
ご覧のとおり、これはどこにも呼び出されません。と確かにではありませんntdll.dll
。
それで、行はどうですか:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
への呼び出しntdll.dll
?
注:実際には ntdll.dll を呼び出しているわけではないことは十分承知しています。したがって、有効な回答には、「Sampling Profiler is misleading. that line is not calling into ntdll.dll.」という言葉を含める必要があります。また、答えは、ほとんどの時間がntdll.dll に費やされていないか、強調表示された行が呼び出し元ではないことを示す必要があります。最後に、Sampling Profiler が間違っている理由と、それを修正する方法を説明する必要があります。
更新 2
ntdll.dll とは? Ntdll は、Windows NT のネイティブ API セットです。Win32 API は、 Windows 1/2/3/9x に存在していた Windows API に似たラッパーntdll.dll
です。実際に ntdll にアクセスするには、直接的または間接的に ntdll を使用する関数を呼び出す必要があります。
たとえば、Delphi アプリケーションがアイドル状態になると、user32.dll 関数を呼び出してメッセージを待機します。
WaitMessage;
実際に見たときは次のとおりです。
USER32.WaitMessage
mov eax,$00001226
mov edx,$7ffe0300
call dword ptr [edx]
ret
で指定された関数を呼び出すこと$7ffe0300
は、Windows が Ring0 に移行する方法であり、EAX で指定された FunctionID を呼び出します。この場合、呼び出されるシステム関数は 0x1226 です。私のオペレーティング システム、Windows Vista では、0x1226 がシステム関数に対応しますNtUserWaitMessage
。
ntdll.dll にアクセスするには、次のようにします。それを呼び出します。
元の質問を言葉にしたとき、私は必死に手を振って無回答を避けようとしていました. 非常に具体的で、私が見ている現実を注意深く指摘することで、人々が事実を無視するのを防ぎ、手を振って議論をしようとしていました.
アップデート 3
私は2つのパラメータを変換しました:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
スタック変数に:
_profiler_WindowLeft := Window.Left;
_profiler_NodeBitmapWidth := NodeBitmap.Width;
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
ボトルネックがないことを確認するには、
Windows.Left
、 また- Nodebitmap.Width
プロファイラーはまだその行を示しています
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
それ自体がボトルネックです。PrepareCell内には何もありません。これは、セルを準備するための呼び出しのセットアップ内、または PrepareCell の開始時の何かであることを意味する必要があります。
VirtualTrees.pas.15746: PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
mov eax,[ebp-$54]
push eax
mov edx,esi
mov ecx,[ebp-$50]
mov eax,[ebp-$04]
call TBasevirtualTree.PrepareCell
ntdll を呼び出すものは何もありません。PrepareCell 自体のプリアンブル:
VirtualTrees.pas.15746: begin
push ebp
mov ebp,esp
add esp,-$44
push ebx
push esi
push edi
mov [ebp-$14],ecx
mov [ebp-$18],edx
mov [ebp-$1c],eax
lea esi,[ebp-$1c]
mov edi,[ebp-$18]
そこには何も呼び出されませんntdll.dll
。
疑問はまだ残っています:
- 1 つの変数をスタックにプッシュし、他の 2 つの変数をレジスタにプッシュすることがボトルネックになるのはなぜですか?
- PrepareCell 自体の内部がボトルネックにならないのはなぜですか?