如何强制GCC生成特定的浮点指令
如何强制GCC生成特定的浮点指令
我正在对这段代码进行 LLVM Clang Apple LLVM 版本 8.0.0 (clang-800.0.42.1) 的反汇编:\n
int main() { float a=0.151234; float b=0.2; float c=a+b; printf("%f", c); }
\n我没有使用 -O 参数进行编译,但我也尝试过使用 -O0(结果相同)和 -O2(实际计算并存储了预计算的值)。\n反汇编的结果如下(我删除了无关的部分):\n
-> 0x100000f30 <+0>: pushq %rbp 0x100000f31 <+1>: movq %rsp, %rbp 0x100000f34 <+4>: subq $0x10, %rsp 0x100000f38 <+8>: leaq 0x6d(%rip), %rdi 0x100000f3f <+15>: movss 0x5d(%rip), %xmm0 0x100000f47 <+23>: movss 0x59(%rip), %xmm1 0x100000f4f <+31>: movss %xmm1, -0x4(%rbp) 0x100000f54 <+36>: movss %xmm0, -0x8(%rbp) 0x100000f59 <+41>: movss -0x4(%rbp), %xmm0 0x100000f5e <+46>: addss -0x8(%rbp), %xmm0 0x100000f63 <+51>: movss %xmm0, -0xc(%rbp) ...
\n显然它的操作如下:\n
- \n
- 将两个浮点数加载到 xmm0 和 xmm1 寄存器中
- 将它们放入栈中
- 从栈中加载一个值(而不是之前 xmm0 中的值)到 xmm0
- 执行加法操作
- 将结果存回栈中
\n
\n
\n
\n
\n
\n我觉得这样做效率低下,因为:\n
- \n
- 所有的操作都可以在寄存器中完成。我之后不会再使用 a 和 b,所以可以跳过任何涉及栈的操作。
- 即使它想使用栈,如果它以不同的顺序进行操作,它可以避免重新从栈中加载 xmm0。
\n
\n
\n鉴于编译器总是正确的,为什么它选择了这个策略呢?
如何强制GCC生成特定的浮点指令
问题的出现原因:
- 默认情况下,-O0(未优化)是默认的编译选项,它告诉编译器要快速编译,而不是花费额外的时间生成高效的代码。
- -O0不是完全没有优化,例如,gcc仍然会消除if(1 == 2){ }块内的代码。特别是gcc比其他大多数编译器更会在-O0时进行诸如除法使用乘法逆元之类的优化,因为它在发出汇编代码之前仍会通过多个内部表示形式转换您的C源代码。
- 即使在-O3下,“编译器总是正确”也是夸大其词的。编译器在大规模上非常出色,但在单个循环内通常会出现一些未优化的情况。循环中浪费的指令(或uop)可能会占用乱序执行重新排序窗口的空间,并且在与另一个线程共享核心时不太友好。有关在某个特定情况下击败编译器的更多信息,请参见C++ code for testing the Collatz conjecture faster than hand-written assembly - why?。
解决方法:
- -O0还意味着将所有变量类似于volatile进行处理以实现一致的调试。这样,您可以设置断点或逐步执行,并修改C变量的值,然后继续执行并使程序按照您在C抽象机器上运行C源代码的预期方式工作。因此,编译器不能进行任何常量传播或值范围简化。(例如,已知为非负的整数可以使用它简化一些操作,或者使一些if条件始终为真或始终为假。)
- 编译器必须针对-O0进行特定的反优化,通过在语句之间将所有变量存储/重新加载到它们的内存地址。(在C和C++中,每个变量都有一个地址,除非它是使用(现在已经过时的)register关键字声明的,从未取过地址。优化去除地址对于其他变量是可能的,但在-O0下不会这样做。)
- 如果不需要这一点,可以使用-Og进行轻量级优化,而无需进行一致调试所需的反优化。GCC手册建议在通常的编辑/编译/运行循环中使用它,但在调试时,许多具有自动存储的局部变量会被“优化掉”。全局变量和函数参数通常仍然具有它们的实际值,至少在函数边界上。
最终,-O0的代码瓶颈通常与-O3的不同,通常是在内存中保持的循环计数器上,创建一个约6个周期的循环依赖链。这可以在编译器生成的汇编代码中产生有趣的效果,例如Adding a redundant assignment speeds up code when compiled without optimization(从汇编的角度来看很有趣,但对于C来说不是)。
相关链接:
- 如果要查看编译器如何添加两个变量,请编写一个带有参数和返回值的函数。记住,您只想查看汇编代码,而不是运行它,因此不需要一个main函数或任何数值文字值,这些都应该是运行时变量。详情请参见How to remove "noise" from GCC/clang assembly output?。
- 考虑到至少clang实际上从一开始就为每个变量分配内存栈。其中一个最早的优化步骤(我猜想在-O0时省略了)将其转换为一堆SSA变量,如果可能的话。因此,至少在clang上,没有进行“反优化”,正常优化仅仅是关闭了。
文章摘自stackoverflow.com,编写者:Peter Cordes,原文链接:How to force GCC to generate specific floating-point instructions