Delphi SampleProfiler: как этот код вызывает ntdll.dll?

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

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

http://i40.tinypic.com/fkmc9j.jpg

После нескольких прогонов, несколько раз, большая часть времени, кажется, проводится внутри 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

Может быть, одна из них - функция или средство получения свойства, которое вызовет 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, или же
  • Node bitmap.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 ответа

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

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

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

ntdll.dll!KiFastSystemCallRet

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

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

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

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

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

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

Другие вопросы по тегам