如何强制GCC生成特定的浮点指令

11 浏览
0 Comments

如何强制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

  1. 将两个浮点数加载到 xmm0 和 xmm1 寄存器中
  2. \n

  3. 将它们放入栈中
  4. \n

  5. 从栈中加载一个值(而不是之前 xmm0 中的值)到 xmm0
  6. \n

  7. 执行加法操作
  8. \n

  9. 将结果存回栈中
  10. \n

\n我觉得这样做效率低下,因为:\n

    \n

  1. 所有的操作都可以在寄存器中完成。我之后不会再使用 a 和 b,所以可以跳过任何涉及栈的操作。
  2. \n

  3. 即使它想使用栈,如果它以不同的顺序进行操作,它可以避免重新从栈中加载 xmm0。
  4. \n

\n鉴于编译器总是正确的,为什么它选择了这个策略呢?

0
0 Comments

如何强制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

0