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