试图理解gcc选项-fomit-frame-pointer

时间:2013-02-02 21:21:47

标签: c performance optimization gcc

我让Google告诉我gcc选项-fomit-frame-pointer的含义,它将我重定向到以下声明。

  

-fomit帧指针

     

不要将帧指针保存在寄存器中以查找不需要的函数。这避免了保存,设置和恢复帧指针的指令;它还在许多功能中提供额外的寄存器。它还使某些机器无法进行调试。

根据我对每个函数的了解,将在进程内存的堆栈中创建激活记录,以保留所有局部变量和更多信息。我希望这个帧指针意味着一个函数的激活记录的地址。

在这种情况下,什么是函数类型,它不需要将帧指针保存在寄存器中?如果我得到这个信息,我会尝试设计基于它的新函数(如果可能),因为如果帧指针没有保存在寄存器中,一些指令将在二进制中省略。在具有许多功能的应用程序中,这将显着提高性能。

2 个答案:

答案 0 :(得分:52)

大多数较小的函数不需要帧指针 - 较大的函数可能需要一个。

这实际上是关于编译器如何设法跟踪堆栈的使用方式,以及堆栈中的内容(局部变量,传递给当前函数的参数以及为即将调用的函数准备的参数)。我不认为很容易表征需要或不需要帧指针的函数(从技术上讲,NO函数有一个帧指针 - 更多的情况是“如果编译器认为有必要降低复杂性其他代码“)。

我不认为你应该“尝试使函数没有帧指针”作为编码策略的一部分 - 就像我说的,简单的函数不需要它们,所以使用-fomit-frame-pointer,和你会再为寄存器分配器提供一个寄存器,并在进入/退出函数时保存1-3条指令。如果你的函数需要一个帧指针,那是因为编译器决定这是一个比不使用帧指针更好的选择。拥有没有帧指针的函数并不是一个目标,它的目标是使代码能够正确和快速地工作。

请注意,“没有帧指针”应该会提供更好的性能,但它并不是一个可以带来巨大改进的神奇子弹 - 尤其是x86-64,它已经有16个寄存器。在32位x86上,因为它只有8个寄存器,其中一个是堆栈指针,并且占用另一个作为帧指针意味着占用了25%的寄存器空间。将其改为12.5%是一个很大的改进。当然,编译64位也会有很大的帮助。

答案 1 :(得分:10)

这就是英特尔平台上的BP / EBP / RBP注册。该寄存器默认为堆栈段(不需要特殊的前缀来访问堆栈段)。

  

EBP是访问堆栈中数据结构,变量和动态分配工作空间的最佳选择。 EBP通常用于相对于堆栈上的固定点而不是相对于当前TOS访问堆栈上的元素。它通常标识为当前过程建立的当前堆栈帧的基址。当在偏移计算中将EBP用作基址寄存器时,在当前堆栈段(即,当前由SS选择的段)中自动计算偏移。由于不必明确指定SS,因此在这种情况下的指令编码更有效。 EBP还可用于索引可通过其他段寄存器寻址的段。

(来源 - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm

由于在大多数32位平台上,数据段和堆栈段是相同的,因此EBP / RBP与堆栈的这种关联不再是问题。在64位平台上也是如此:AMD在2003年推出的x86-64架构在很大程度上放弃了对64位模式分段的支持:四个段寄存器:CS,SS,DS和ES被强制为0 x86 32位和64位平台的这些情况实质上意味着可以在访问存储器的处理器指令中使用EBP / RBP寄存器,而无需任何前缀。

因此,您编写的编译器选项允许将BP / EBP / RBP用于其他方式,例如:持有一个局部变量。

通过“这避免了保存,设置和恢复帧指针的指令”意味着避免在每个函数的条目上使用以下代码:

push ebp
mov ebp, esp

enter指令,这对英特尔80286和80386处理器非常有用。

此外,在函数返回之前,使用以下代码:

mov esp, ebp
pop ebp 

leave指令。

调试工具可以扫描堆栈数据并在定位call sites时使用这些推送的EBP寄存器数据,即以按层次调用它们的顺序显示函数名称和参数。

程序员可能对堆栈帧的问题不是很广泛(堆栈中的单个实体只提供一个函数调用并保留返回地址,参数和局部变量),但从狭义上说 - 当术语在编译器选项的上下文中提到了stack frames。从编译器的角度来看,堆栈帧只是例程入口和出口代码,它将一个锚推送到堆栈 - 它也可以用于调试和异常处理。调试工具可以扫描堆栈数据并使用这些锚点进行反向跟踪,同时在堆栈中定位call sites,即按照分层次调用它们的顺序显示函数的名称。

这就是为什么了解程序员在编译器选项方面的堆栈框架是非常重要的 - 因为编译器可以控制是否生成此代码。

在某些情况下,编译器可以省略堆栈帧(例程的入口和出口代码),并且可以通过堆栈指针(SP / ESP / RSP)直接访问变量,而不是方便的基址指针(BP / ESP / RSP)。 编译器省略某些功能的堆栈帧的条件可能不同,例如:(1)该函数是叶函数(即不调用其他函数的终端实体); (2)不使用例外; (3)没有用栈上的传出参数调用例程; (4)该功能没有参数。

省略堆栈帧(例程的进入和退出代码)可以使代码更小更快,但也可能会对调试器对堆栈中的数据进行反向跟踪并将其显示给程序员的能力产生负面影响。这些是编译器选项,用于确定函数应在哪些条件下满足,以便编译器使用堆栈帧条目和退出代码对其进行授予。例如,在以下情况下,编译器可以选择将此类入口和退出代码添加到函数中:(a)始终,(b)从不,(c)在需要时(指定条件)。

从一般性返回到特殊性:如果您将使用-fomit-frame-pointer GCC编译器选项,您可以获得例程的入口和出口代码,以及有额外的寄存器(除非它已经打开)默认情况下,它本身或其他选项隐式地使用,在这种情况下,您已经从使用EBP / RBP寄存器获益中受益,如果它已经隐式显示,则不会通过显式指定此选项获得额外的增益。但请注意,在16位和32位模式下,BP寄存器无法像AX具有(AL和AH)那样访问它的8位部分。

由于此选项,除了允许编译器在优化中使用EBP作为通用寄存器外,还可以防止为堆栈帧生成退出和输入代码,这会使调试变得复杂 - 这就是为什么{{3显式状态(异常强调粗体)启用此选项使某些机器无法进行调试

另请注意,与调试或优化相关的其他编译器选项可能会隐式地将-fomit-frame-pointer选项设置为ON或OFF。

我在gcc.gnu.org上找不到关于其他选项如何影响x86平台上的-fomit-frame-pointer 的官方信息GCC documentation仅声明以下内容:

  

-O也会在机器上启用-fomit-frame-pointer,这样做不会干扰调试。

因此,如果您只是在x86平台上使用单个-fomit-frame-pointer选项进行编译,那么从文档本身开始是否会打开{em}是不明确的。它可以根据经验进行测试,但在这种情况下,GCC开发人员不会承诺将来不改变此选项的行为,恕不另行通知。

但是,https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html在评论中指出x86-16平台和x86-32 / 64平台之间-O的默认设置存在差异。

这个选项 - -fomit-frame-pointer - 也是Peter Cordes,不仅仅是GCC:

对于英特尔编译器,此选项具有别名-fomit-frame-pointer

以下是英特尔撰写的文章:

  

这些选项确定EBP是否在优化中用作通用寄存器。选项-fomit-frame-pointer和/ Oy允许这种用法。选项-fno-omit-frame-pointer和/ Oy-禁止它。

     

有些调试器希望EBP用作堆栈帧指针,除非是这样,否则不能产生堆栈回溯。 -fno-omit-frame-pointer和/ Oy- options指示编译器生成代码,该代码维护并使用EBP作为所有函数的堆栈帧指针,以便调试器仍然可以生成堆栈回溯而不执行以下操作:

     

对于-fno-omit-frame-pointer:使用-O0关闭优化   对于/ Oy-:关闭/ O1,/ O2或/ O3优化   指定选项-O0或-g选项时,将设置-fno-omit-frame-pointer选项。指定选项-O1,-O2或-O3时,将设置-fomit-frame-pointer选项。

     

指定/ O1,/ O2或/ O3选项时,将设置/ Oy选项。选择/ Oy-在指定/ Od选项时设置。

     

使用-fno-omit-frame-pointer或/ Oy-选项可将通用寄存器的数量减少1,从而导致代码效率稍低。

     

注意对于Linux *系统:目前GCC 3.2异常处理存在问题。因此,当为C ++安装GCC 3.2并打开异常处理时(默认情况下),英特尔编译器会忽略此选项。

请注意,上述引用仅适用于英特尔C ++ 15编译器,而不适用于GCC。