Пытаемся понять параметр gcc -fomit-frame-pointer

Я попросил Google дать мне значение параметра gcc -fomit-frame-pointer, который перенаправляет меня к приведенному ниже утверждению.

-fomit-frame-pointer

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

Насколько мне известно о каждой функции, в стеке памяти процесса будет создана запись активации для хранения всех локальных переменных и некоторой дополнительной информации. Я надеюсь, что этот указатель кадра означает адрес записи активации функции.

В этом случае, каковы типы функций, для которых не нужно хранить указатель кадра в регистре? Если я получу эту информацию, я попытаюсь разработать новую функцию на ее основе (если возможно), потому что, если указатель кадра не хранится в регистрах, некоторые инструкции будут опущены в двоичном формате. Это действительно заметно улучшит производительность в приложении, в котором много функций.


person rashok    schedule 02.02.2013    source источник
comment
Необходимости отладки только одного аварийного дампа кода, который был скомпилирован с этой опцией, будет достаточно, чтобы заставить вас исключить эту опцию из ваших make-файлов. Между прочим, он не удаляет никаких инструкций, он просто дает оптимизатору еще один регистр для работы с хранилищем.   -  person Hans Passant    schedule 03.02.2013
comment
@HansPassant На самом деле, он очень полезен для релизных сборок. Наличие двух целей в Makefile - Release и Debug на самом деле очень полезно, возьмите этот вариант в качестве примера.   -  person Kotauskas    schedule 12.05.2019
comment
@VladislavToncharov Я думаю, вам никогда не приходилось отлаживать аварийный дамп от клиента, который запускает вашу Release-build?   -  person Andreas Magnusson    schedule 30.04.2020


Ответы (2)


Большинству небольших функций не нужен указатель кадра - он МОЖЕТ потребоваться для более крупных функций.

На самом деле речь идет о том, насколько хорошо компилятор отслеживает, как используется стек и где что-то находится в стеке (локальные переменные, аргументы, переданные в текущую функцию, и аргументы, подготовленные для функции, которая должна быть вызвана). Я не думаю, что легко охарактеризовать функции, которым нужен или не нужен указатель кадра (технически НИКАКАЯ функция НЕ ДОЛЖНА иметь указатель кадра - это скорее случай, «если компилятор сочтет необходимым уменьшить сложность другой код »).

Я не думаю, что вам следует «пытаться сделать функции без указателя кадра» как часть вашей стратегии кодирования - как я уже сказал, простым функциям они не нужны, поэтому используйте -fomit-frame-pointer, и вы получите еще один регистр доступны для распределителя регистров, и сохраните 1-3 инструкции по входу / выходу в функции. Если вашей функции нужен указатель кадра, это потому, что компилятор решает, что это лучший вариант, чем не использовать указатель кадра. Это не цель - иметь функции без указателя кадра, это цель - иметь код, который работает как правильно, так и быстро.

Обратите внимание, что «отсутствие указателя кадра» должно дать лучшую производительность, но это не какая-то волшебная палочка, которая дает огромные улучшения - особенно на x86-64, у которого для начала уже есть 16 регистров. В 32-битной системе x86, поскольку она имеет только 8 регистров, один из которых является указателем стека, а другой занимает место в качестве указателя кадра, это означает, что занято 25% регистрового пространства. Изменить это значение на 12,5% - это неплохое улучшение. Конечно, компиляция для 64-битной версии тоже очень поможет.

person Mats Petersson    schedule 02.02.2013
comment
Обычно компилятор может отслеживать глубину стека самостоятельно и не нуждается в указателе кадра. Исключение составляют случаи, когда функция использует alloca, который перемещает указатель стека на переменную величину. Отсутствие указателя кадра значительно усложняет отладку. Локальные переменные труднее найти, а трассировки стека гораздо сложнее восстановить без помощи указателя кадра. Кроме того, доступ к параметрам может стать более дорогостоящим, поскольку они находятся далеко от вершины стека и могут потребовать более дорогих режимов адресации. - person Raymond Chen; 03.02.2013
comment
Да, итак, если мы не используем alloca [кто использует? - Я на 99% уверен, что я никогда не писал код, который использует alloca] или variable size local arrays [что является современной формой alloca], тогда компилятор МОЖЕТ все же решить, что использование указателя кадра является лучшим вариантом, потому что компиляторы написаны так, чтобы не слепо следуйте предложенным вариантам, но предлагайте вам лучший выбор. - person Mats Petersson; 03.02.2013
comment
@MatsPetersson VLA отличается от alloca: они выбрасываются, как только вы покидаете область, в которой они объявлены, тогда как alloca пространство освобождается только при выходе из функции. Я думаю, это делает VLA намного проще, чем alloca. - person Jens Gustedt; 03.02.2013
comment
Возможно, стоит упомянуть, что gcc по умолчанию включен -fomit-frame-pointer для x86-64. - person zwol; 03.02.2013
comment
@JensGustedt, проблема не в том, что они выбрасываются, проблема в том, что их размер (например, alloca'ed space) неизвестен во время компиляции. Обычно компилятор использует указатель кадра для получения адреса локальных переменных, если размер кадра стека не изменяется, он может найти их по фиксированному смещению от указателя стека. - person vonbrand; 03.02.2013
comment
@zwol Наверное, вы забыли упомянуть, что это действительно так, когда включена оптимизация, вне зависимости от архитектуры x86. - person Stefan; 24.10.2019
comment
@Stefan Во время моего комментария (пять лет назад) это было не верно для всех архитектур. В последнее время я не уделял пристального внимания разработке GCC, возможно, сейчас это правда. - person zwol; 24.10.2019
comment
Просто примечание на случай, если кого-то волнует, делает ли GCC fomit-frame-pointer по умолчанию при включенном флаге оптимизации - gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html - person HCSF; 18.12.2019

Это все о реестре BP / EBP / RBP на платформах Intel. По умолчанию в этом регистре используется сегмент стека (не требуется специальный префикс для доступа к сегменту стека).

EBP - лучший выбор регистра для доступа к структурам данных, переменным и динамически выделяемому рабочему пространству в стеке. EBP часто используется для доступа к элементам в стеке относительно фиксированной точки в стеке, а не относительно текущего TOS. Обычно он определяет базовый адрес текущего кадра стека, установленного для текущей процедуры. Когда EBP используется в качестве базового регистра при вычислении смещения, смещение вычисляется автоматически в текущем сегменте стека (т. Е. Сегменте, выбранном в данный момент SS). Поскольку SS не нужно указывать явно, кодирование инструкций в таких случаях более эффективно. EBP также может использоваться для индексации сегментов, адресуемых через другие регистры сегментов.

(источник - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm)

Поскольку на большинстве 32-битных платформ сегмент данных и сегмент стека одинаковы, эта ассоциация EBP / RBP со стеком больше не является проблемой. То же самое и на 64-битных платформах: архитектура x86-64, представленная AMD в 2003 году, в значительной степени отказалась от поддержки сегментации в 64-битном режиме: четыре из сегментных регистров: CS, SS, DS и ES принудительно установлены на 0. Эти обстоятельства 32-битных и 64-битных платформ x86 по существу означают, что регистр EBP / RBP может использоваться без какого-либо префикса в инструкциях процессора, которые обращаются к памяти.

Таким образом, параметр компилятора, о котором вы писали, позволяет использовать BP / EBP / RBP для других целей, например, для хранения локальной переменной.

Под этим подразумевается избегание инструкций по сохранению, настройке и восстановлению указателей кадров, подразумевается избегание следующего кода при вводе каждой функции:

push ebp
mov ebp, esp

или инструкция enter, которая была очень полезна на процессорах Intel 80286 и 80386.

Также перед возвратом функции используется следующий код:

mov esp, ebp
pop ebp 

или инструкция leave.

Инструменты отладки могут сканировать данные стека и использовать эти переданные данные регистров EBP при нахождении call sites, то есть для отображения имен функции и аргументов в том порядке, в котором они были вызваны иерархически.

У программистов могут возникнуть вопросы о кадрах стека не в широком смысле (что это единый объект в стеке, который обслуживает только один вызов функции и сохраняет адрес возврата, аргументы и локальные переменные), а в узком смысле - когда упоминается термин stack frames в контексте параметров компилятора. С точки зрения компилятора, кадр стека - это просто код входа и выхода для подпрограммы, который подталкивает якорь к стеку - который также можно использовать для отладки и для обработки исключений. Инструменты отладки могут сканировать данные стека и использовать эти якоря для обратной трассировки, находя call sites в стеке, то есть отображать имена функций в том же порядке, в котором они были вызваны иерархически.

Вот почему программисту важно понимать, что такое стековый фрейм с точки зрения параметров компилятора - потому что компилятор может контролировать, генерировать этот код или нет.

В некоторых случаях кадр стека (код входа и выхода для процедуры) может быть опущен компилятором, а доступ к переменным будет осуществляться напрямую через указатель стека (SP / ESP / RSP), а не через удобный базовый указатель (BP / ESP / RSP). Условия, при которых компилятор пропускает кадры стека для некоторых функций, могут быть разными, например: (1) функция является листовой функцией (т. Е. Конечной сущностью, которая не вызывает другие функции); (2) не используются исключения; (3) никакие процедуры не вызываются с исходящими параметрами в стеке; (4) функция не имеет параметров.

Пропуск фреймов стека (код входа и выхода для процедуры) может сделать код меньше и быстрее. Тем не менее, они также могут отрицательно повлиять на способность отладчиков отслеживать данные стека и отображать их программисту. Это параметры компилятора, которые определяют, при каких условиях функция должна удовлетворять, чтобы компилятор присудил ей код входа и выхода из кадра стека. Например, у компилятора могут быть варианты добавления такого кода входа и выхода к функциям в следующих случаях: (а) всегда, (б) никогда, (в) при необходимости (с указанием условий).

Возвращаясь от общих к частностям: если вы используете параметр компилятора -fomit-frame-pointer GCC, вы можете выиграть как по коду входа, так и по коду выхода для процедуры, а также по наличию дополнительного регистра (если он уже не включен по умолчанию либо сам по себе, либо неявно другими параметрами. в этом случае вы уже получаете выгоду от использования регистра EBP / RBP, и никакого дополнительного выигрыша не будет получено путем явного указания этой опции, если она уже включена неявно). Однако обратите внимание, что в 16-битном и 32-битном режимах регистр BP не имеет возможности предоставлять доступ к 8-битным его частям, как это имеет AX (AL и AH).

Поскольку этот параметр, помимо разрешения компилятору использовать EBP в качестве регистра общего назначения при оптимизации, также предотвращает создание кода выхода и входа для кадра стека, что усложняет отладку, поэтому документация GCC явно указывает (необычно выделено жирным шрифтом), что включение этой опции делает невозможным отладку на некоторых машинах.

Также имейте в виду, что другие параметры компилятора, связанные с отладкой или оптимизацией, могут неявно включать или выключать параметр -fomit-frame-pointer.

Я не нашел официальной информации на gcc.gnu.org о том, как другие параметры влияют на -fomit-frame-pointer на платформах x86, https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html заявляет только следующее:

-O также включает -fomit-frame-pointer на машинах, где это не мешает отладке.

Поэтому неясно, из документации как таковой, будет ли -fomit-frame-pointer включен, если вы просто компилируете с одним параметром `-O 'на платформе x86. Его можно проверить эмпирически, но в этом случае разработчики GCC не обязуются не изменять поведение этой опции в будущем без предварительного уведомления.

Однако Питер Кордес указал в комментариях, что есть разница в настройках по умолчанию -fomit-frame-pointer между x86- 16 платформ и платформы x86-32 / 64.

Этот параметр - -fomit-frame-pointer - также относится к Intel C ++ Compiler 15.0, а не только в GCC:

Для компилятора Intel у этого параметра есть псевдоним /Oy.

Вот что об этом написала Intel:

Эти параметры определяют, используется ли EBP как универсальный регистр при оптимизации. Опции -fomit-frame-pointer и / Oy позволяют это использовать. Опции -fno-omit-frame-pointer и / Oy- запрещают это.

Некоторые отладчики ожидают, что EBP будет использоваться в качестве указателя кадра стека, и не могут производить обратную трассировку стека, если это не так. Параметры -fno-omit-frame-pointer и / Oy- предписывают компилятору сгенерировать код, который поддерживает и использует EBP в качестве указателя кадра стека для всех функций, чтобы отладчик мог по-прежнему производить обратную трассировку стека, не выполняя следующие действия:

Для -fno-omit-frame-pointer: отключение оптимизаций с помощью -O0. Для / Oy-: отключение / O1, / O2 или / O3 оптимизации. Параметр -fno-omit-frame-pointer устанавливается, когда вы указываете параметр - O0 или параметр -g. Параметр -fomit-frame-pointer устанавливается, когда вы указываете параметр -O1, -O2 или -O3.

Параметр / Oy устанавливается при указании параметра / O1, / O2 или / O3. Параметр / Oy- устанавливается при указании параметра / Od.

Использование параметра -fno-omit-frame-pointer или / Oy- уменьшает количество доступных регистров общего назначения на 1 и может привести к несколько менее эффективному коду.

ПРИМЕЧАНИЕ Для систем Linux *: в настоящее время существует проблема с обработкой исключений GCC 3.2. Поэтому компилятор Intel игнорирует этот параметр, если для C ++ установлен GCC 3.2 и включена обработка исключений (по умолчанию).

Имейте в виду, что приведенная выше цитата актуальна только для компилятора Intel C ++ 15, а не для GCC.

person Maxim Masiutin    schedule 14.07.2017
comment
16-битный код и значение BP по умолчанию для SS вместо DS, на самом деле не актуально для gcc. gcc -m16 существует, но это странный частный случай, который в основном создает 32-битный код, который работает в 16-битном режиме, используя повсюду префиксы. Также обратите внимание, что -fomit-frame-pointer был включен по умолчанию в течение многих лет на x86 -m32 и дольше, чем на x86-64 (-m64). - person Peter Cordes; 14.07.2017
comment
@PeterCordes - спасибо, я обновил правки в соответствии с поднятыми вами проблемами. - person Maxim Masiutin; 15.07.2017
comment
Отличный ответ! - person Seth Johnson; 19.03.2021