我使用Delphi Sampling Profiler分析了我的部分应用程序。 Like most people,我看到ntdll.dll
内部花费了大部分时间。
注意:我将选项设置为忽略
Application.Idle
时间,并从System.pas
拨打电话。所以 不在ntdll
内,因为 应用程序闲置:
多次运行多次后,大部分时间似乎花在ntdll.dll
内,但奇怪的是调用者是谁:
来电者来自Virtual Treeview:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
注意:该应用程序不在
ntdll.dll
内,因为 应用程序是空闲的,因为 来电者不是Application.Idle
。
让我感到困惑的是,本身这一行(即 PrepareCell中的内容)是调用者ntdll
。更令人困惑的是:
PrepareCell()
PrepareCell
的 setup (例如弹出堆栈变量,设置隐式异常帧等),这是调用者。这些内容会在Profiler中显示为PrepareCell内begin
的热点。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
可能其中一个是调用ntdll
的函数或属性获取器。所以我在线上放了一个断点,并在运行时查看CPU窗口:
alt text http://i44.tinypic.com/2ut0pkx.jpg
那里的 一行可能是罪魁祸首:
call dword ptr [edx+$2c]
但是,当我按照跳转时,它不会在ntdll.dll
中结束,而是TBitmap.GetWidth
:
alt text http://i44.tinypic.com/2uswzlc.jpg
正如你所看到的那样,不会在任何地方打电话;当然不会进入ntdll.dll
。
那么这条线是怎么回事:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
调用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;
当你真正看到它时:
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
或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
。
问题仍然存在:
答案 0 :(得分:3)
嗯,这个问题实际上是我制作自己的采样分析器的主要原因:
http://code.google.com/p/asmprofiler/wiki/AsmProfilerSamplingMode
也许不完美,但你可以尝试一下。让我知道你对它的看法。
不过,我认为这与几乎所有调用都会结束对内核的调用(内存请求,绘制事件等)这一事实有关。只有计算才需要调用内核。 大多数调用都以等待内核结果结束:ntdll.dll!KiFastSystemCallRet
您可以在Process Explorer中使用线程堆栈视图,或在Delphi中,或在AsmProfiler的“实时视图”中使用StackWalk64 API来查看: http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer
答案 1 :(得分:0)
那里可能发生了两件事。
第一个是SamplingProfiler通过向上移动堆栈来识别调用者,直到它从Delphi代码中遇到看起来像Delphi的有效调用点。
问题是,某些程序可能会立即保留大量堆栈,而无需重新初始化它。这可能会导致误报。唯一的线索就是最近调用了你的假阳性。
第二个事情是ntdll
本地化,这是肯定的,但是,ntdll是你在用户空间的等待点,而作为user197220,ntdll就是你要去的地方最终等待大部分时间你正在调用系统并等待结果。
在您的情况下,除非您降低采样率,否则您将看到247ms的CPU工作时间,如果这些247个样本是在几秒钟的实时时间内收集的,则可能会空闲。由于假阳性指向VirtualTree油漆准备,我的赌注是ntdll时间实际上是油漆时间(驱动程序或操作系统软件)。 您可以尝试注释掉实际完成绘画的代码。