【问题标题】:How exactly does gcc do optimizations?gcc 究竟是如何进行优化的?
【发布时间】:2016-06-13 17:48:56
【问题描述】:

为了知道gcc到底是怎么优化的,我写了两个用-O2编译的程序,但是汇编代码有一些不同。在我的程序中,我想在循环中输出“hello”,并在每个输出之间添加一些延迟。这两个程序仅用于说明我的问题,我知道我可以在程序 1 中使用 volatile 或 asm 来实现我的目的。

程序 1

#include <stdio.h>

int main(int argc, char **argv)
{
    unsigned long i = 0;
    while (1) {
        if (++i > 0x1fffffffUL) {
            printf("hello\n");
            i = 0;
        }
    }
}

用-O2编译,汇编代码为:

Disassembly of section .text.startup:

00000000 <_main>:
#include <stdio.h>

int main(int argc, char **argv)
{
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 e4 f0                and    $0xfffffff0,%esp
   6:   83 ec 10                sub    $0x10,%esp
   9:   e8 00 00 00 00          call   e <_main+0xe>
   e:   66 90                   xchg   %ax,%ax
  10:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
  17:   e8 00 00 00 00          call   1c <_main+0x1c>
  1c:   eb f2                   jmp    10 <_main+0x10>
  1e:   90                      nop
  1f:   90                      nop

程序 2

int main(int argc, char **argv)
{
    unsigned long i = 0;
    while (1) {
        if (i > 0x1fffffffUL) {
            printf("hello\n");
            i = 0;
        }
        i++;
    }
}

用-O2编译,汇编代码为:

Disassembly of section .text.startup:

00000000 <_main>:
#include <stdio.h>

int main(int argc, char **argv)
{
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 e4 f0                and    $0xfffffff0,%esp
   6:   83 ec 10                sub    $0x10,%esp
   9:   e8 00 00 00 00          call   e <_main+0xe>
   e:   31 c0                   xor    %eax,%eax
  10:   83 c0 01                add    $0x1,%eax
  13:   3d ff ff ff 1f          cmp    $0x1fffffff,%eax
  18:   76 f6                   jbe    10 <_main+0x10>
  1a:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
    while (1) {
        if (i > 0x1fffffffUL) {
            printf("hello\n");
            i = 0;
        }
        i++;
  21:   e8 00 00 00 00          call   26 <_main+0x26>

int main(int argc, char **argv)
{
    unsigned long i = 0;
    while (1) {
        if (i > 0x1fffffffUL) {
  26:   31 c0                   xor    %eax,%eax
  28:   eb e6                   jmp    10 <_main+0x10>
            printf("hello\n");
  2a:   90                      nop
  2b:   90                      nop
  2c:   90                      nop
  2d:   90                      nop
  2e:   90                      nop
  2f:   90                      nop

在程序1中,优化了i的增加,但在程序2中没有,为什么会出现这种情况?使用 -O2 对这两个程序进行优化时,gcc 使用什么规则?

【问题讨论】:

  • 可能是因为你的程序永远循环?
  • 我认为这是因为通常很难在编译时检测到逻辑对行为没有影响。在某些极少数情况下是可能的,在大多数情况下不是。您的程序 1 似乎是幸运的案例之一。程序 2 不是。
  • 请随时在 gcc 的 bugzilla 上提交增强请求。请注意,当您测试优化时,您应该避免使用 main 函数:编译器知道它只会被调用一次,因此有时会对其进行较少的优化(显然不是这次)。
  • 这是一个bug,我已经把它归档到gcc的bugzilla,参考here。我想它可能会在以后修复。

标签: c gcc optimization compiler-optimization


【解决方案1】:

询问优化器的“为什么”通常是浪费时间,因为没有优化器运行的“规则”——除了“好像”:优化器可能不会改变可观察的符合代码的行为。

两个程序的“可观察行为”都是重复打印“hello”。

在您的第一个程序中,计数被优化掉,使可观察的行为发生得更快。 这是优化器的工作。很高兴你的代码现在更高效了!

在您的第二个程序中,计数没有被优化掉,因为不知何故优化器 -- 在 this 版本的 this 编译器中带有 this 设置——没有看到它可以没有它。为什么?谁知道(除了编译器优化器模块的维护者)?

如果您的期望行为是在输出之间有延迟,请使用thrd_sleep() 之类的东西。空计数循环是在 C64 上延迟 BASIC 2.0 程序的一种方法,但它们不应该在 C 中使用,因为您刚刚观察到的确切原因是:您永远不知道优化器做了什么。

【讨论】:

    【解决方案2】:

    if 语句中的分支现在取决于循环的上一次迭代中发生的事情。特别是,编译器可以在程序 1 中轻松确定 i 在 while 循环的每次迭代中递增(因为它位于顶部),而在程序 2 中并非如此。

    无论如何,编译器优化是非常复杂的。见下文:

    gcc -O2 是这些标志的快捷方式:(来自documentation

          -fauto-inc-dec 
          -fbranch-count-reg 
          -fcombine-stack-adjustments 
          -fcompare-elim 
          -fcprop-registers 
          -fdce 
          -fdefer-pop 
          -fdelayed-branch 
          -fdse 
          -fforward-propagate 
          -fguess-branch-probability 
          -fif-conversion2 
          -fif-conversion 
          -finline-functions-called-once 
          -fipa-pure-const 
          -fipa-profile 
          -fipa-reference 
          -fmerge-constants 
          -fmove-loop-invariants 
          -freorder-blocks 
          -fshrink-wrap 
          -fsplit-wide-types 
          -fssa-backprop 
          -fssa-phiopt 
          -ftree-bit-ccp 
          -ftree-ccp 
          -ftree-ch 
          -ftree-coalesce-vars 
          -ftree-copy-prop 
          -ftree-dce 
          -ftree-dominator-opts 
          -ftree-dse 
          -ftree-forwprop 
          -ftree-fre 
          -ftree-phiprop 
          -ftree-sink 
          -ftree-slsr 
          -ftree-sra 
          -ftree-pta 
          -ftree-ter 
          -funit-at-a-time
          -fthread-jumps 
          -falign-functions  -falign-jumps 
          -falign-loops  -falign-labels 
          -fcaller-saves 
          -fcrossjumping 
          -fcse-follow-jumps  -fcse-skip-blocks 
          -fdelete-null-pointer-checks 
          -fdevirtualize -fdevirtualize-speculatively 
          -fexpensive-optimizations 
          -fgcse  -fgcse-lm  
          -fhoist-adjacent-loads 
          -finline-small-functions 
          -findirect-inlining 
          -fipa-cp 
          -fipa-cp-alignment 
          -fipa-sra 
          -fipa-icf 
          -fisolate-erroneous-paths-dereference 
          -flra-remat 
          -foptimize-sibling-calls 
          -foptimize-strlen 
          -fpartial-inlining 
          -fpeephole2 
          -freorder-blocks-algorithm=stc 
          -freorder-blocks-and-partition -freorder-functions 
          -frerun-cse-after-loop  
          -fsched-interblock  -fsched-spec 
          -fschedule-insns  -fschedule-insns2 
          -fstrict-aliasing -fstrict-overflow 
          -ftree-builtin-call-dce 
          -ftree-switch-conversion -ftree-tail-merge 
          -ftree-pre 
          -ftree-vrp 
          -fipa-ra
    

    这些标志中的每一个都对应于编译器允许进行的不同可能的优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-23
      • 1970-01-01
      • 1970-01-01
      • 2011-02-12
      • 2013-03-18
      • 1970-01-01
      • 2021-10-16
      • 2018-06-03
      相关资源
      最近更新 更多