【问题标题】:Does omitting the frame pointers really have a positive effect on performance and a negative effect on debug-ability?省略帧指针真的对性能有正面影响,对调试能力有负面影响吗?
【发布时间】:2024-01-08 20:27:01
【问题描述】:

正如很久以前所建议的,我总是在没有帧指针的情况下构建我的发布可执行文件(如果使用 /Ox 编译,这是默认设置)。

但是,现在我在论文http://research.microsoft.com/apps/pubs/default.aspx?id=81176 中读到,帧指针对性能没有太大影响。因此,完全优化它(使用 /Ox)或使用帧指针完全优化它(使用 /Ox /Oy-)对性能并没有真正的影响。

微软似乎表示添加帧指针 (/Oy-) 会使调试更容易,但真的是这样吗?

我做了一些实验,发现:

  • 在一个简单的 32 位测试可执行文件(使用 /Ox /Ob0 编译)中,省略帧指针确实提高了性能(大约 10%)。但是这个测试可执行文件只执行一些函数调用,没有别的。
  • 在我自己的应用程序中,添加/删除帧指针似乎没有太大影响。添加帧指针似乎可以使应用程序快 5% 左右,但这可能在误差范围内。

关于帧指针的一般建议是什么?

  • 是否应该在发布的可执行文件中省略它们 (/Ox),因为它们确实对性能有积极影响?
  • 是否应该将它们添加 (/Ox /Oy-) 到发布的可执行文件中,因为它们可以提高调试能力(使用故障转储文件进行调试时)?

使用 Visual Studio 2010。

【问题讨论】:

  • 函数调用是它唯一优化的东西,每次调用只有几个指令。它确实减少了所需的堆栈空间(除非您进行深度递归,否则这无关紧要)
  • 如果你知道堆栈指针在哪里,我总觉得帧指针有点多余(对于编译器来说,这很容易)。
  • 我假设“10%”的速度优势来自 32 位 x86,它只有 7 个通用寄存器(不包括堆栈指针),因此将其中 1 个用作帧指针是很重要。
  • @PeterCordes 你是对的。过去几年我不再考虑帧指针,因为我们现在都是 64 位的(并且提到的编译器选项在 64 位 Visual Studio 中不存在)。寄存器的数量确实可以在其中发挥重要作用。
  • GCC 在任何模式下都支持-fno-omit-frame-pointer(对于非 x86 目标)。这是一个众所周知的事实,即 1 个可用寄存器的增量值增加的总数越少,来自 CS 论文已经探索了这一点,例如为各种模拟机器编译 SpecInt 并查看代码大小和动态指令数。但是,在 x86(包括 x86-64)上,帧指针有时可以在经常引用堆栈变量的代码中节省代码大小; [rbp+-disp8] 的编码比[rsp +- disp8] 小。因此,有时省略 FP 反而会带来伤害而不是帮助。

标签: performance visual-studio-2010 visual-c++ x86 stack-frame


【解决方案1】:

简答:通过省略帧指针,

您需要使用堆栈指针来访问局部变量和参数。编译器不介意,但是如果您使用汇编进行编码,这会使您的生活变得更加艰难。如果你不使用宏,那就更难了。

每个函数调用可以节省四个字节(32 位架构)的堆栈空间。除非您使用深度递归,否则这不是胜利。

您将内存写入保存到缓存内存(堆栈),并且(理论上)在函数进入/退出时节省了几个时钟滴答,但您可以增加代码大小。除非您的函数很少经常执行(在这种情况下应该内联),否则这不应该引起注意。

您释放了一个通用寄存器。如果编译器可以使用该寄存器,它将生成更小且可能更快的代码。但是,如果大部分 CPU 时间都花在与主内存(甚至是硬盘驱动器)通信上,那么省略帧指针并不能帮助您摆脱这种情况。

调试器将失去生成堆栈跟踪的简单方法。调试器可能仍然能够从不同的源(例如PDB file)生成堆栈跟踪。


长答案:

典型的函数入口和出口是:

PUSH SP   ;push the frame pointer
MOV FP,SP ;store the stack pointer in the frame pointer
SUB SP,xx ;allocate space for local variables et al.
...
LEAVE     ;restore the stack pointer and pop the old frame pointer
RET       ;return from the function

没有堆栈指针的入口和出口可能如下所示:

SUB SP,xx ;allocate space for local variables et al.
...
ADD SP,xx ;de-allocate space for local variables et al.
RET       ;return from the function.

您将保存两条指令,但您还复制了一个文字值,因此代码不会变短(恰恰相反),但您可能已经保存了几个时钟周期(或者没有,如果它导致缓存未命中指令缓存)。不过,您确实在堆栈上节省了一些空间。


您确实释放了一个通用寄存器。这只有好处。

在 regcall/fastcall 中,这是一个额外的寄存器,您可以在其中存储函数的参数。因此,如果您的函数需要七个(在 x86 上;在大多数其他架构上更多)或更多参数(包括this),则第七个参数仍然适合寄存器。更重要的是,同样适用于局部变量。数组和大对象不适合寄存器(但指向它们的指针),但如果您的函数使用七个不同的局部变量(包括计算复杂表达式所需的临时变量),编译器可能会生成更小的代码.更小的代码意味着更少的指令缓存占用空间,这意味着更低的未命中率,从而更少的内存访问(但Intel Atom has a 32K instruction cache,这意味着你的代码可能无论如何都适合)。

x86 架构具有[BX/BP/SI/DI][BX/BP + SI/DI] 寻址模式。这使得 BP 寄存器成为缩放数组索引非常有用的地方,尤其是当数组指针驻留在 SI 或 DI 寄存器中时。两个偏移寄存器比一个好。

使用寄存器可以避免内存访问,但如果一个变量值得存储在寄存器中,那么它很可能在 L1 缓存中仍然存在(尤其是因为它将在堆栈上)。移入/移出缓存仍有成本,但由于现代 CPU 做了很多移动优化和并行化,L1 访问可能与寄存器访问一样快。因此,不移动数据带来的速度优势仍然存在,但没有那么大。我可以很容易地想象 CPU 完全避免了数据缓存,至少就读取而言(并且写入缓存可以并行完成)。

使用的寄存器是需要保留的寄存器。如果您在再次使用它之前无论如何都要把它推入堆栈,那么在寄存器中存储太多是不值得的。在按调用者保留的调用约定(例如上面的约定)中,这意味着寄存器作为持久存储在经常调用其​​他函数的函数中没有那么有用。

另外请注意,x86 为浮点寄存器提供了一个单独的寄存器空间,这意味着无论如何如果没有额外的数据移动指令,浮点数就无法使用 BP 寄存器。只有整数和内存指针可以。


省略帧指针会失去可调试性。 This answer 说明原因:

如果代码崩溃,调试器需要做的就是生成堆栈跟踪:

    PUSH FP      ; log the current frame pointer as well
$1: CALL log_FP  ; log the frame pointer currently on stack
    LEAVE        ; pop the frame pointer to get the next one
    CMP [FP+4],0
    JNZ $1       ; until the stack cannot be popped (the return address is some specific value)

如果代码在没有帧指针的情况下崩溃,调试器可能无法生成堆栈跟踪,因为它可能不知道(即,它需要定位函数入口/出口点)需要从堆栈指针。如果调试器不知道帧指针没有被使用,它甚至可能会崩溃。

【讨论】:

  • 当我们使用 minidump 进行调试时,调试器仍然可以使用 PDB 文件找出如何遍历堆栈。这还不够吗?在堆栈为空的极少数情况下(因为有调用或返回到可执行代码之外的位置),使用帧指针是否有帮助?
  • wikipedia article about PDB 链接到an article on MSDN,其中指出“符号文件可能包含...帧指针遗漏 (FPO) 记录”。这表明 PDB 文件足以生成堆栈跟踪。但是,当代码跳转到随机位置(调用非函数)时,这可能还不够。好点 - 不知道 PDB 文件。
  • 您的自己的代码可能有 PDB,但第 3 方代码没有。此外,我认为 FPO 的另一个胜利是释放寄存器以供其他用途。
  • @MarkSowul 我不认为这是一场胜利。如果第 3 方代码省略了帧指针并且缺少 FPO 记录,那么当您的应用程序崩溃时,您所知道的只是它在第 3 方代码段中崩溃了。但是,你不知道是谁的代码(如果多个 3rd 方犯了同一个罪),你也不知道为什么 - 是因为该代码中的错误,还是因为你发送了虚假指针(“存储我的数据到这个(只读部分/堆栈上曾经属于我的地方*/我最近释放的对象),拜托”)?
  • 老实说,我希望“简短回答”要短得多,比如“是”或“否”。
最近更新 更多