I have profiled part of my application using the Delphi Sampling Profiler . Like most people , I see most of the time spent inside ntdll.dll .
Note: I have included options to ignore Application.Idle and calls from System.pas . So this is not inside ntdll because the application is inactive:

After several starts, several times, most of the time seems to be spent inside ntdll.dll , but it is strange who the caller is:

Virtual Treeview's Caller:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
Note: the application is not located inside ntdll.dll because the application is not used, because the caller is not Application.Idle .
What bothers me is that this line itself (i.e. not something inside PrepareCell) is the ntdll caller in ntdll . Even more confusing is that:
- not only is it not something inside
PrepareCell() - it's not even setting up a
PrepareCell (like push stack variables, setting implicit exception frames, etc.), which is the caller. These things will appear in the profiler as an access point in begin inside PrepareCell.
VirtualTrees.pas:
procedure TBaseVirtualTree.PrepareCell(var PaintInfo: TVTPaintInfo; WindowOrgX, MaxWidth: Integer); begin ... end;
Therefore, I am trying to understand how this line is:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
calls ntdll.dll .
The only other ways are three parameters:
PaintInfoWindow.LeftNodeBitmap.Width
Maybe one of them is the function or ntdll getting the property that ntdll will ntdll . So I set a breakpoint on the line and looked at the processor window at runtime:

There is a line that could be the culprit:
call dword ptr [edx+$2c]
But when I follow this jump, it does not end in ntdll.dll , but TBitmap.GetWidth :

Which, as you can see, does not call anywhere; and of course not in ntdll.dll .
So, how does the line go:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
to call ntdll.dll ?
Note: I know well that this is not really a ntdll.dll call. Therefore, any correct answer should include the words "Sampling Profiler is misleading; this line does not cause ntdll.dll." The answer should also either say that most of the time is not spent in ntdll.dll, or that the highlighted line is not calling. Finally, any answer should explain why the Sampling Profiler is wrong and how this can be fixed.
Update 2
What is ntdll.dll? Ntdll is a set of APIs for Windows NT. The Win32 API is a wrapper around ntdll.dll that looks like the Windows API that existed in Windows 1/2/3 / 9x. To actually get into ntdll, you must call a function that uses ntdll directly or indirectly.
For example, when my Delphi application is idle, it waits for a message by calling the user32.dll function:
WaitMessage;
When, when you actually look at this:
USER32.WaitMessage mov eax,$00001226 mov edx,$7ffe0300 call dword ptr [edx] ret
Calling the function specified in $7ffe0300 is the way that Windows goes into Ring0 by calling the FunctionID specified in EAX. In this case, the system function 0x1226 is called. On my Windows Vista operating system, 0x1226 corresponds to the NtUserWaitMessage system function.
This is how you get into ntdll.dll: you name it.
I was desperate to avoid manually rejecting the answer when I formulated the initial question. Being very specific, carefully pointing out the reality of what I see, I tried to stop people from ignoring the facts and tried to use a waving argument.
Update three
I converted two parameters:
PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);
to stack variables:
_profiler_WindowLeft := Window.Left; _profiler_NodeBitmapWidth := NodeBitmap.Width; PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
To confirm that the bottleneck is not
Windows.Left , or- Nodebitmap.Width
The profiler still indicates that the line
PrepareCell(PaintInfo, _profiler_WindowLeft, _profiler_NodeBitmapWidth);
itself is a bottleneck; there is nothing inside PrepareCell. This should mean that it is something inside the call setup to prepare the cell or at the beginning of 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
Nothing in this calls ntdll. Now the preamble in PrepareCell itself:
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]
Nothing invokes ntdll.dll .
Questions still remain:
- why is putting one variable on the stack and two others on registers a bottleneck?
- why is nothing inside the PrepareCell bottleneck?