【问题标题】:GCC optimization?海合会优化?
【发布时间】:2015-04-29 09:19:12
【问题描述】:

我使用的是 GCC 4.8.1。我正在尝试通过将某些代码放入嵌套循环中来对其速度进行基准测试,如下例所示。每当我这样做时,它都会在最短的时间内(如 0.02 秒)执行,使用 -03 或没有任何优化,无论有多少次迭代。这有什么原因吗?我确信它工作正常,因为值总是正确的,如果我在循环中使用printf,那么它会按预期运行。

int main()
{
    int i,j,k;
    int var;
    int big_num = 1000000;
    int x[1];

    for (i = 0;i<big_num;++i){
        for (j = 0;j<big_num;++j){
            for (k = 0;k<big_num;++k){
               // any short code fragment such as:
               var = i - j + k; 
               x[0] = var;
            }
        }
    }
    return 0;
}

【问题讨论】:

    标签: c gcc optimization


    【解决方案1】:

    您编辑的问题并非如此:您的代码正在声明一个单元素数组 int x[1]; 并使用越界索引访问它(索引应小于 1 但非负数,因此可以只能是 0) 作为x[1];这是典型的undefined behavior,编译器可以通过发出任何类型的代码来合法地优化它。

    顺便说一句,GCC 4.9(在我的 Debian/Sid/x86-64 上)正在(正确地)将您的代码优化为空的 main strong>(因为没有有用的计算发生);您可以通过使用gcc -fverbose-asm -O2 -S 编译并查看生成的*.s 程序集文件来检查这一点;如果您真的对优化过程中的各种内部表示感到好奇,请使用 -fdump-tree-all 进行编译;您还可以更改编译器的行为(或添加一些检查通道),例如用MELT扩展它

    您可以通过将x[0] = var; 替换为x[0] += var; 并在main 结束时对x[0] 产生副作用,从而使您的计算变得有意义,例如printf("%d\n", x[0]);return x[0] != 0;。然后编译器可能会生成一个循环(它可能会在编译时计算循环的结果,但我认为 GCC 不够聪明)。

    最后,当前典型的微处理器通常是无序的和超标量的,因此每个周期执行一条以上的指令(时钟频率至少为 2GHz)。所以他们每秒运行数十亿次基本操作。您通常需要一个基准持续半秒以上(否则测量没有足够的意义)并重复几次基准。因此,您需要编写执行数十亿(即超过 1010)个基本 C 操作的基准测试代码。并且您需要该代码有用(具有副作用或在其他地方使用的结果计算),否则编译器将通过删除它来进行优化。此外,基准测试代码应该接受一些输入(否则编译器可能会在编译时进行大量计算)。在您的情况下,您可能会初始化 bignum 喜欢

    int main (int argc, char**argv) {
      int big_num = (argc>1)?atoi(argv[1]):1000000;
    

    【讨论】:

    • 我正要说同样的话 - 还建议分配给 x[1] 可能会破坏堆栈并覆盖存储在 big_num 中的值。
    • OP 也特别说没有优化,通常没有优化(-O0gcc 不会用 UB 做这样的事情,我似乎无法得到godbolt 上的行为与 OP 看到的相同。也许 OP 关于不使用优化是不正确的,如果是这种情况,那么问题应该被关闭。
    【解决方案2】:

    如何设置优化? 对我来说,它有效(big_num = 1000):

    $ gcc -o x -O0 x.c && time ./x
    ./x  2.08s user 0.00s system 99% cpu 2.086 total
    $ gcc -o x -O1 x.c && time ./x 
    ./x  0.31s user 0.00s system 99% cpu 0.309 total
    $ gcc -o x -O2 x.c && time ./x  
    ./x  0.00s user 0.00s system 0% cpu 0.000 total
    

    【讨论】:

    • 谢谢,这也适用于我。我只尝试过 -03 并通过 CodeBlocks 取消选中所有优化器选项(我将不得不编辑问题)。有趣的是,不选中它们似乎在这种情况下与 -03 一样优化。
    • @NathanSchmidt 这确实是唯一正确的答案,您的结果有意义的唯一方法是在每次运行时使用一些优化级别。
    【解决方案3】:

    您的代码实际上并没有做任何事情。 GCC 和大多数编译器都非常聪明。它可以查看它,确定它没有可见的效果并将其完全删除。

    【讨论】:

      【解决方案4】:

      您的代码没有副作用:它不会通过网络发送任何内容,也不会写入文件,因此 gcc 忽略了该代码。现代 gcc 版本有 -fdump-* 选项,允许记录编译器的每个阶段:

      $ gcc -O2 -fdump-tree-all elide.c
      

      之后gcc会生成一堆输出文件:

      $ ls -1 | head
      a.out
      elide.c
      elide.c.001t.tu
      elide.c.003t.original
      elide.c.004t.gimple
      elide.c.007t.omplower
      ...
      

      比较它们可能会揭示代码被删除的阶段。在我的情况下(GCC 4.8.1),它是cddce 阶段。来自 GCC 源文件gcc/tree-ssa-dce.c

      /* Dead code elimination.
      
      References:
      
          Building an Optimizing Compiler,
          Robert Morgan, Butterworth-Heinemann, 1998, Section 8.9.
      
          Advanced Compiler Design and Implementation,
          Steven Muchnick, Morgan Kaufmann, 1997, Section 18.10.
      
      Dead-code elimination is the removal of statements which have no
      impact on the program's output.  "Dead statements" have no impact
      on the program's output, while "necessary statements" may have
      impact on the output.
      
      The algorithm consists of three phases:
      1. Marking as necessary all statements known to be necessary,
          e.g. most function calls, writing a value to memory, etc;
      2. Propagating necessary statements, e.g., the statements
          giving values to operands in necessary statements; and
      3. Removing dead statements.  */
      

      您可以通过将变量标记为volatile 来显式破坏优化器:

      volatile int i,j,k;
      volatile int var;
      volatile int big_num = 1000000;
      volatile int x[1];
      

      volatile 会告诉编译器,写入内存单元有副作用

      【讨论】:

        猜你喜欢
        • 2022-11-12
        • 2011-09-20
        • 2011-08-03
        • 2011-11-29
        • 1970-01-01
        • 2011-12-30
        • 2015-01-11
        • 2014-01-17
        • 1970-01-01
        相关资源
        最近更新 更多