我用Delphi采样分析器描述了我应用程序的一部分.像大多数人一样,我看到大部分时间都花在了里面ntdll.dll
.
注意:我打开选项忽略
Application.Idle
时间,并从中调用System.pas
.所以它不在里面,ntdll
因为应用程序是空闲的:
在多次运行多次之后,大部分时间似乎花在了内部ntdll.dll
,但奇怪的是调用者是谁:
来电者来自Virtual Treeview:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Run Code Online (Sandbox Code Playgroud)
注意:应用程序不在内部,
ntdll.dll
因为应用程序处于空闲状态,因为调用者不是Application.Idle
.
令我困惑的是,这条线本身(即不是PrepareCell 内部的东西)是调用者ntdll
.更令人困惑的是:
PrepareCell()
PrepareCell
(如弹出堆栈变量,建立隐含例外帧等),这是主叫方.这些东西会在探查器中显示为begin
PrepareCell内部的热点.VirtualTrees.pas:
procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
begin
...
end;
Run Code Online (Sandbox Code Playgroud)
所以我想弄清楚这一行:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Run Code Online (Sandbox Code Playgroud)
正在打电话ntdll.dll
.
其他唯一的方法是三个参数:
PaintInfo
Window.Left
NodeBitmap.Width
也许其中一个是函数或属性getter,它会调用ntdll
.所以我在线上放了一个断点,并在运行时查看CPU窗口:
alt text http://i44.tinypic.com/2ut0pkx.jpg
还有就是在那里一条线,可能是罪魁祸首:
call dword ptr [edx+$2c]
Run Code Online (Sandbox Code Playgroud)
但是当我跟着那个跳跃时,它并没有结束ntdll.dll
,但是TBitmap.GetWidth
:
替代文字http://i44.tinypic.com/2uswzlc.jpg
正如你所看到的那样,在任何地方都不会打电话; 当然不是ntdll.dll
.
那么这条线怎么样:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Run Code Online (Sandbox Code Playgroud)
呼唤ntdll.dll
?
注意:我完全知道它并没有真正调用ntdll.dll.所以任何有效的答案都必须包括"采样分析器误导;该行没有调用ntdll.dll".答案还必须要么说大多数时间没有花在ntdll.dll上,或者突出显示的行不是调用者.最后,任何答案都必须解释为什么Sampling Profiler是错误的,以及如何修复它.
什么是ntdll.dll文件?Ntdll是Windows NT的本机API集.Win32 API是一个包装器ntdll.dll
,看起来像 Windows 1/2/3/9x中存在的Windows API.为了实际进入ntdll,你必须直接或间接调用一个使用ntdll的函数.
例如,当我的Delphi应用程序空闲时,它会通过调用user32.dll函数来等待消息:
WaitMessage;
Run Code Online (Sandbox Code Playgroud)
当你真正看到它的时候是:
USER32.WaitMessage
mov eax,$00001226
mov edx,$7ffe0300
call dword ptr [edx]
ret
Run Code Online (Sandbox Code Playgroud)
调用指定的函数$7ffe0300
是Windows转换为Ring0的方式,调用EAX中指定的FunctionID.在这种情况下,被调用的系统函数是0x1226.在我的操作系统上,Windows Vista,0x1226对应于系统功能NtUserWaitMessage
.
这是你如何进入ntdll.dll:你称之为.
当我说出原来的问题时,我正拼命地试图避免挥手无法回答.通过非常具体,仔细地指出我所看到的现实,我试图阻止人们忽视事实,并试图用挥手的论点.
我转换了两个参数:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Run Code Online (Sandbox Code Playgroud)
进入堆栈变量:
_profiler_WindowLeft := Window.Left;
_profiler_NodeBitmapWidth := NodeBitmap.Width;
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
Run Code Online (Sandbox Code Playgroud)
确认瓶颈不是呼叫
Windows.Left
, 要么Profiler仍然表示该行
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
Run Code Online (Sandbox Code Playgroud)
本身就是瓶颈; 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
Run Code Online (Sandbox Code Playgroud)
没有任何内容可以调用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]
Run Code Online (Sandbox Code Playgroud)
没有任何东西可以进入ntdll.dll
.
问题仍然存在:
好吧,这个问题实际上是我制作自己的采样分析器的主要原因:
http://code.google.com/p/asmprofiler/wiki/AsmProfilerSamplingMode
也许并不完美,但你可以尝试一下。让我知道你的想法。
顺便说一句,我认为这与几乎所有调用都以对内核的调用(内存请求、绘制事件等)结束这一事实有关。仅计算不需要调用内核。大多数调用都会以等待内核结果结束:
ntdll.dll!KiFastSystemCallRet
Run Code Online (Sandbox Code Playgroud)
您可以在带有线程堆栈视图的 Process Explorer 中看到这一点,或者在 Delphi 中,或者在我的 AsmProfiler 的“实时视图”中使用 StackWalk64 API:
http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer