Delphi SampleProfiler: как этот код вызывает ntdll.dll? - PullRequest
8 голосов
/ 12 апреля 2010

Я профилировал часть своего приложения, используя Delphi Sampling Profiler . Как и большинство людей , я вижу большую часть времени, проведенного внутри ntdll.dll.

Примечание: я включил опции на игнорировать Application.Idle время и звонки с System.pas. Так что не внутри ntdll, потому что приложение бездействует:

альтернативный текст http://i40.tinypic.com/fkmc9j.jpg

После нескольких прогонов, несколько раз, большая часть времени, кажется, проводится внутри ntdll.dll, но странно то, кто звонит:

enter image description here

Звонящий из виртуального дерева:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    

Примечание: Приложение не находится внутри ntdll.dll, потому что приложение бездействует, потому что звонящий не Application.Idle.

Что меня смущает, так это то, что именно эта строка сама (т.е. не что-то внутри PrepareCell) является вызывающей стороной для ntdll. Еще более запутанным является то, что:

  • не только не что-то внутри PrepareCell()
  • это даже не setup из 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

Может быть, одна из них - это функция или метод получения свойства, который вызовет ntdll. Поэтому я поставил точку останова на линии и посмотрел на окно процессора во время выполнения:

альтернативный текст http://i44.tinypic.com/2ut0pkx.jpg

Там есть строка, которая может быть виновником:

call dword ptr [edx+$2c]

Но когда я следую за этим прыжком, он не заканчивается ntdll.dll, а TBitmap.GetWidth:

альтернативный текст http://i44.tinypic.com/2uswzlc.jpg

Который, как вы видите, никуда не звонит; и, конечно, не в ntdll.dll.


Так как же линия:

PrepareCell(PaintInfo, Window.Left, NodeBitmap.Width);    

звонит в ntdll.dll?


Примечание: Я прекрасно знаю, что на самом деле это не вызов ntdll.dll. Поэтому любой правильный ответ должен включать слова «Sampling Profiler вводит в заблуждение; эта строка не вызывает ntdll.dll». Ответ также должен либо сказать, что большую часть времени не тратится в ntdll.dll, либо что выделенная строка не является вызывающей стороной. Наконец, любой ответ должен объяснить, почему Sampling Profiler неправильный и как его можно исправить.

Обновление 2

Что такое ntdll.dll? Ntdll - это собственный набор API для Windows NT. Win32 API - это оболочка вокруг ntdll.dll, которая выглядит как Windows API, существовавший в Windows 1/2/3 / 9x. Чтобы на самом деле попасть в ntdll, вы должны вызвать функцию, которая использует ntdll прямо или косвенно.

Например, когда мое приложение Delphi бездействует, оно ожидает сообщения, вызывая функцию user32.dll:

WaitMessage;

Когда вы на самом деле смотрите на это:

USER32.WaitMessage
  mov eax,$00001226
  mov edx,$7ffe0300
  call dword ptr [edx]
  ret

Вызов функции, указанной в $7ffe0300, является способом, которым Windows переходит в Ring0, вызывая FunctionID, указанный в EAX. В этом случае вызывается системная функция 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

Профилировщик все еще указывает, что строка

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 ничего не узкое место?

Ответы [ 2 ]

3 голосов
/ 12 апреля 2010

Ну, на самом деле эта проблема была моей главной причиной, чтобы сделать мой собственный профилировщик выборки:
http://code.google.com/p/asmprofiler/wiki/AsmProfilerSamplingMode

Может быть, не идеально, но вы можете попробовать. Дайте мне знать, что вы об этом думаете.

Кстати, я думаю, что это связано с тем, что почти все вызовы заканчиваются вызовами ядра (запросы памяти, события рисования и т. Д.). Только для расчетов не нужно вызывать ядро. Большинство вызовов заканчивается ожиданием результатов ядра:

ntdll.dll!KiFastSystemCallRet

Вы можете увидеть это в Process Explorer с представлением стека потоков, или в Delphi, или используя StackWalk64 API в моем «Живом представлении» AsmProfiler:
http://code.google.com/p/asmprofiler/wiki/ProcessStackViewer

0 голосов
/ 28 июля 2010

Там, вероятно, происходят две вещи.

Первое заключается в том, что SamplingProfiler идентифицирует вызывающего по ходу вверх по стеку до тех пор, пока из кода Delphi он не встретит нечто похожее на действительную точку вызова в Delphi.

Дело в том, что некоторые процедуры могут зарезервировать большое количество стека сразу, без его повторной инициализации. Это может привести к ложному срабатыванию. Единственная подсказка в том, что ваш ложный положительный результат был недавно вызван.

Вторая вещь - это локализация ntdll, которая наверняка известна, однако ntdll - это ваша точка ожидания в пользовательском пространстве, и, как user197220, ntdll - это то, где вы в конечном итоге ждете Большую часть времени вы звоните системным сотрудникам и ждете результата.

В вашем случае, если вы не уменьшите частоту дискретизации, вы смотрите на 247 мс времени работы ЦП, которое, вероятно, могло бы стать бездействующим, если бы эти 247 сэмплов были собраны за много секунд реального времени. Поскольку ложные положительные моменты при подготовке к рисованию VirtualTree, я бы поспорил, что время ntdll - это фактически время рисования (драйвер или программное обеспечение ОС). Вы можете попробовать закомментировать код, который действительно рисует, чтобы быть уверенным.

...