用于-fno-function-cse
不对函数地址执行公共子表达式消除。 GCC 手册:
-fno-function-cse
不要将函数地址放在寄存器中;使每条调用常量函数的指令都明确包含函数的地址。
此选项会导致代码效率降低,但是在不使用此选项时执行的优化可能会混淆一些改变汇编器输出的奇怪技巧。
默认为 -ffunction-cse
如何找到特定的 GCC 选项
我查看了gcc -O1 -fverbose-asm
asm 输出以查看所有-O1
暗示的优化选项(GCC 在 asm 注释中列出)。 -O1 -fno-...
将所有内容的版本编译为仅 3条指令,每个指令都带有符号名称,确认其中一条是我想要的,所以我只需要通过平分该选项call
列表来缩小范围-fno-
我使用了具有 MSP430 GCC6.2.1、测试代码 + asm的 Godbolt 编译器资源管理器。我禁用了“comments”过滤器选项,所以我可以在 asm 输出中看到纯注释行。
由于有很多选项,我过去常常tr ' ' '\n' | sed -e 's/-f/-fno-/' -e '/;/d'
将-f
选项变成它们的否定形式。我将整个 asm 注释块复制/粘贴到终端中的该命令中,然后将结果复制/粘贴到 Godbolt 上的 GCC 选项框中。(与-O1
.一起-O0
是一种用于一致调试的特殊反优化模式,因此即使使用正确的选项,跨语句优化也可能永远不会处于活动状态-O0
。这就是为什么我需要否定选项而不是尝试没有 的积极形式-O1
)
然后我选择并删除了一堆选项,看看是否改变了 asm。如果没有,请继续。当我找到一个块时,我知道我想要的选项在那里,所以我可以撤消 (control-z) 并删除所有其他-f
选项,然后将其缩小到一个。(当我-fno-function-cse
在该组中看到名称时,我认为这听起来像是正确的事情。幸运的是,如果您知道编译器/优化术语,GCC 选项确实具有有意义的名称。)
这比一次只看一个选项或翻阅手册要快,因为我什至不确定这些特定选项中的任何一个都能控制这一点。
顺便说一句,GCC 不会对大多数其他 ISA 进行代码大小优化,因为这对它们来说不是性能上的胜利。代码大小不是 x86-64 甚至 ARM thumb 上性能的最重要因素;间接跳转的可能分支错误预测的额外成本(以及分支预测器的额外污染)超过了代码大小的成本。
这是x86 上的代码大小胜利,其中可以为多个 2 字节指令设置5 字节mov
立即或 7 字节 RIP 相对(x86-64)。lea
call
对于许多固定指令宽度的 ISA,如 AArch64 或 ARM(Thumb 模式除外),它通常甚至不是代码大小的胜利,其中标准代码模型假设函数在相对分支和链接的范围内(呼叫)指示。因此,调用任何函数都需要一条指令,与任何其他指令的大小相同。
即使-ffunction-cse
显式启用,GCC 也不会对 x86-64 或 ARM thumb 进行这种优化,即使在它已经使用 GOT 中的函数指针的情况下也是如此。(Godbolt 上的 x86-64 gcc-Os -fPIE -fno-plt -ffunction-cse
。我什至告诉 GCC 优化代码大小;保存/恢复像 RBX 这样的调用保留寄存器以用于 2 字节call rbx
而不是 6 字节call [RIP+rel32]
,即使在需要额外的指令之后也会节省大小推送/弹出 RBX(每个 1 字节)并加载到 RBX(一个具有 RIP 相对寻址模式的 mov)。)
这可能被认为是错过的优化-Os
,尤其是对于“简单”内核的 ARM Thumb,-mcpu=cortex-m3
甚至可能没有分支预测器。
(AArch64会将函数指针加载到带有 , 的寄存器中-fPIE -fno-plt
,用于没有“隐藏”可见性的函数,即该函数可能仅位于共享库中。即使使用-fno-function-cse
. https://godbolt.org/z/f3MP56 也会发生这种情况。 )