Delphi SampleProfiler: как этот код вызывает ntdll.dll?
Я профилировал часть своего приложения, используя Delphi Sampling Profiler. Как и большинство людей, я вижу большую часть времени, проведенного внутри ntdll.dll
,
Примечание: я включил параметры, чтобы игнорировать
Application.Idle
время и звонки изSystem.pas
, Так что не внутриntdll
потому что приложение бездействует:
После нескольких прогонов, несколько раз, большая часть времени, кажется, проводится внутри 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 - это фактически время рисования (драйвер или программное обеспечение ОС). Вы можете попробовать закомментировать код, который на самом деле делает рисование, чтобы быть уверенным.