我使用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
例如弹出堆栈变量,设置隐式异常帧等)。这些东西会在分析器中显示为begin
PrepareCell 内部的热点。
VirtualTrees.pas:
procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer);
begin
...
end;
所以我想弄清楚这条线是怎么回事:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
正在打电话ntdll.dll
。
唯一的其他方式是三个参数:
PaintInfo
Window.Left
NodeBitmap.Width
也许其中之一是一个函数,或者一个属性 getter,它会调用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 具有误导性;该行没有调用 ntdll.dll”。答案也必须要么说大部分时间没有花在 ntdll.dll 中,要么突出显示的行不是调用者。最后,任何答案都必须解释为什么 Sampling Profiler 是错误的,以及如何修复它。
更新 2
什么是 ntdll.dll?Ntdll 是 Windows NT 的原生 API 集。Win32 API 是一个包装器ntdll.dll
,看起来就像存在于 Windows 1/2/3/9x 中的 Windows API。为了真正进入 ntdll,你必须调用一个直接或间接使用 ntdll 的函数。
例如,当我的 Delphi 应用程序空闲时,它通过调用 user32.dll 函数等待消息:
WaitMessage;
当你真正看到它时:
USER32.WaitMessage
mov eax,$00001226
mov edx,$7ffe0300
call dword ptr [edx]
ret
调用 at 指定的函数$7ffe0300
是 Windows 转换到 Ring0 的方式,调用 EAX 中指定的 FunctionID。在这种情况下,被调用的系统函数是 0x1226。在我的操作系统 Windows Vista 上,0x1226 对应系统功能NtUserWaitMessage
。
这就是你进入 ntdll.dll 的方法:你调用它。
当我说出最初的问题时,我拼命地试图避免挥手不回答。通过非常具体,仔细指出我所看到的现实,我试图防止人们忽视事实,并试图使用挥手的论据。
更新三
我转换了两个参数:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
进入堆栈变量:
_profiler_WindowLeft := Window.Left;
_profiler_NodeBitmapWidth := NodeBitmap.Width;
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
确认瓶颈不是调用
Windows.Left
, 或者- 节点位图宽度
Profiler 仍然指示该行
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
。
问题仍然存在:
- 为什么将一个变量推入堆栈,而将另外两个变量推入寄存器是瓶颈?
- 为什么 PrepareCell 内部的任何东西都不是瓶颈?