На прошлой неделе я болтал с одним парнем из команды измерения производительности. Он мне рассказал историю, которая меня очень удивила. Как оказалось, они обнаружили проблему производительности в одном стороннем драйвере. К сожалению, у них возникли проблемы в локализации узкого места, поскольку разработчик драйвера скомпилировал его с FPO (Frame Pointer Ommission) и не предоставил отладочную информацию.
Что тут удивительного? Ну, я удивился что сегодня вообще кто-то использует FPO.
Но что такое FPO?
Чтобы понять ответ, нам нужно вернуться назад во времени.
Процессор Intel 8088 имел чрезвычайно мало регистров, вот они (я игнорировал сегментные регистры):
AX | BX | CX | DX | IP |
SI | DI | BP | SP | FLAGS |
Даже в таком ограниченном наборе регистров этим регистрам были заданы специальные роли. Регистры
AX
, BX
, CX
и DX
были регистрами "общего назначения", SI
и DI
были "индексными" регистрами, SP
был "указателем стека", BP
был "указателем фрейма" ("указателем базы"), IP
был "указателем инструкции", а FLAGS
был регистром только для чтения, который содержал информацию о текущем состоянии процессора.Регистры
BX
, SI
, DI
и BP
были специальными, потому что они могли использоваться как "индексные" регистры. Индексные регистры чрезвычайно важны для компилятора, поскольку только они могут использоваться в получении доступа к памяти через указатель. Другими словами, если у вас есть структура, расположенная по смещению $1234
в памяти, вы можете установить индексный регистр в значение $1234
и получать доступ к значениям, относительно этого адреса (и регистра). Например:MOV BX, [Structure] MOV AX, [BX]+4Мы записываем в регистр
BX
значение памяти, на которое указывает [Structure]
, а затем записываем в регистр AX
слово, находящееся в 4 байте с начала этой структуры.Здесь нужно отметить, что регистр
SP
не являлся индексным регистром. Это означало, что для получения доступа к локальным переменным и аргументам на стеке вам нужно использовать другой регистр - и именно так появился регистр BP
. Индексный регистр BP
был создан специально для получения доступа к значениям в стеке.Когда вышел 386, разработчики Intel расширили регистры до 32 бит, а также сняли ограничение, что только
BX
, SI
, DI
и BP
могли использоваться как индексные:EAX | EBX | ECX | EDX | EIP |
ESI | EDI | EBP | ESP | FLAGS |
Это неплохо: неожиданно, вместо жалких трёх регистров, компилятор мог использовать все шесть.
Поскольку индексные регистры используются для доступа к полям структур, для компилятора они на вес золота - чем их больше, тем лучше, так что стоит пойти на многое, лишь бы их было побольше.
Некий очень умный человек сообразил, что раз теперь
ESP
является индексным регистром, то EBP
больше не является единственным специально выделенным регистром для стека. Другими словами, вместо:
MyFunction: PUSH EBP MOV EBP, ESP SUB ESP, <LocalVariableStorage> MOV EAX, [EBP+8] : : MOV ESP, EBP POP EBP RETDвы могли получить доступ к первому параметру на стеке следующим образом (
EBP
+ 0 - старое значение EBP
, EBP
+ 4 - адрес возврата):
MyFunction: SUB SP, <LocalVariableStorage> MOV EAX, [ESP+4+Это работает на отлично, теперь] : : ADD SP, <LocalVariableStorage> RETD
EBP
освобождается и его можно использовать как дополнительный регистр общего назначения! Такая оптимизация в компиляторе называется "Frame Pointer Omission", известная под акронимом FPO.Но с FPO есть небольшая проблема.
Если вы посмотрите на первый вариант кода (без FPO), то заметите, что первой инструкцией в функции будет
PUSH EBP
, за которым будет MOV EBP, ESP
. Здесь получается интересный и крайне полезный (побочный) эффект. Фактически, при этом получается односвязный список, в котором хранятся указатели на каждый фрейм для всех вызывающих функции. Таким образом, зная значение EBP
внутри одной функции, вы можете получить стек вызовов для этой функции. Это невероятно полезно для отладки, потому что это означает, что стеки вызовов всегда точны - даже если у вас на руках нет отладочной информации. К сожалению, если вы начинаете использовать FPO, список фреймов теряется - эта информация просто не отслеживается.Чтобы решить эту проблему, информация, которая теряется при использовании FPO, заносится в отладочную информацию. Таким образом, если у вас есть отладочная информация ("символы"), то вы сможете построить стек вызовов.
FPO был включен для всех модулей Windows, начиная с NT 3.51. Но начиная с Windows Vista FPO был отключен - поскольку он стал не нужен: машины стали значительно быстрее машин 1995 года, так что небольшой выигрыш в производительности от FPO не покрывал его пенальти на отладку и анализ.
Комментариев нет:
Отправить комментарий
Можно использовать некоторые HTML-теги, например:
<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>
Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.