【发布时间】:2010-04-12 15:17:39
【问题描述】:
我使用Delphi Sampling Profiler 分析了我的应用程序的一部分。 Like most people,我看到大部分时间都花在了ntdll.dll。
注意:我打开了忽略
Application.Idle时间的选项,并从System.pas调用。所以 不在ntdll内,因为 应用程序处于空闲状态:
经过多次运行,多次,大部分时间似乎都在ntdll.dll内部度过,但奇怪的是调用者是谁:
调用者来自虚拟树视图:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
注意:应用程序不在
ntdll.dll中,因为 应用程序是空闲的,因为 来电者不是Application.Idle。
让我困惑的是,这行本身(即不是inside 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。
唯一的其他方式是三个参数:
PaintInfoWindow.LeftNodeBitmap.Width
其中一个可能是函数或属性获取器,它会调用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
调用$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,或 - Nodebitmap.Width
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 本身不是瓶颈?
【问题讨论】:
-
并非每个函数调用都以 ntdll.dll 结尾。 CPU 不能只是神奇地跳入 ntdll。 ntdll 是本机 Windows NT API。为了进入 ntdll,你必须实际调用一些调用 ntdll 的东西。
mov ebp,esp不会调用ntdll。它只是不是。