【问题标题】:When code optimization happens?何时进行代码优化?
【发布时间】:2012-02-26 21:04:05
【问题描述】:

昨天,我接受了采访。他们问我何时进行代码优化? 说,

int abc;//Global variable
abc = 3;
if(abc == 3)
{
  printf("abc will be always 3");
}
else
{
  printf("This will never executed");
}

现在的问题是优化何时发生? A... 在运行时 B... 在编译时。 我在编译时回答了......对我来说,我认为编译器会在编译时检查 volatile 关键字。如果变量未声明为 volatile,则它会优化代码。但是,当编译器知道这一点时,这个变量永远不会是 3? 如果它是在运行时,那么当编译器知道变量永远不会是 3 时?因为如果在这部分代码执行后要更改变量。请解开我的疑惑

【问题讨论】:

    标签: c optimization runtime compile-time


    【解决方案1】:

    不回答问题,而是举一个编译时优化的例子。当被要求这样做时,gcc 会优化代码。 -O(优化)选项可以在不同级别进行优化。它可以用作-O1、-O2 和-O3。 gcc 手册页准确地描述了每个级别的含义。

    -S 选项将 C 转换为汇编并保存在 .s 文件中。

    test.c

    #include <stdio.h>
    
    int abc;//Global variable
    
    void main()
    {
        abc = 3;
        if(abc == 3)
            printf("abc will be always 3");
        else
            printf("This will never executed");
    }
    

    没有 gcc 优化两个字符串出现在汇编代码上。

    $ gcc -S test.c;cat test.s

        .file   "test.c"
        .comm   abc,4,4
        .section    .rodata
    .LC0:
        .string "abc will be always 3"
    .LC1:
        .string "This will never executed"
        .text
        .globl  main
        .type   main, @function
    main:
    .LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $3, abc(%rip)
        movl    abc(%rip), %eax
        cmpl    $3, %eax
        jne .L2
        movl    $.LC0, %eax
        movq    %rax, %rdi
        movl    $0, %eax
        call    printf
        jmp .L1
    .L2:
        movl    $.LC1, %eax
        movq    %rax, %rdi
        movl    $0, %eax
        call    printf
    .L1:
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.6.1 20110908 (Red Hat 4.6.1-9)"
        .section    .note.GNU-stack,"",@progbits
    

    Whit gcc level 1 optimization 只有一个字符串被翻译成汇编

    $ gcc -O1 -S test.c;cat test.s

        .file   "test.c"
        .section    .rodata.str1.1,"aMS",@progbits,1
    .LC0:
        .string "abc will be always 3"
        .text
        .globl  main
        .type   main, @function
    main:
    .LFB11:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    $3, abc(%rip)
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        addq    $8, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
    .LFE11:
        .size   main, .-main
        .comm   abc,4,4
        .ident  "GCC: (GNU) 4.6.1 20110908 (Red Hat 4.6.1-9)"
        .section    .note.GNU-stack,"",@progbits
    

    【讨论】:

      【解决方案2】:

      最简单的答案: 代码优化发生在撰写本文时。

      简单的答案: 也许,给定范围,它取决于编译器。

      很难回答: 给定全局范围,编译器应该不理会它,假设它可以在文件的其他地方访问。多次通过可以优化它。如果编译器不认为文件是静态的(想想具有平面内存模型的系统),那么全局是真正的全局,并且假设应该是任何可能改变值的东西。这就是为什么要避免使用全局变量,除非意图确实是全局访问。

      根据处理器,分支预测参数是否适用,但主要是编译时间或根本不适用。

      ps:我真的不喜欢这样的面试题。

      【讨论】:

      • 同意你的回答,但又出现了一个疑问,如果全局变量(非易失性)可以随时更改,那么我们为什么需要易失性?
      • volatile 对于显示为静态的上下文是必需的。想想指针传递的值。此外,如果您直接映射硬件寄存器(想想外围存储器映射或设备寄存器),则值可以在不运行任何代码的情况下更改。
      【解决方案3】:

      我想知道他们是在寻找一个答案还是几个可能的答案。

      编译器通常在运行时不做任何事情。编译器二进制文件及其组件不需要在运行时环境中存在(对于语言 C)。

      消除这种死分支的优化类型将使用某些技术来编写优化编译器。请参阅 Muchnik 关于编译器的书。使用的技术可能涉及创建基本块的有向图,然后使用以下技术之一:

      • 默认使用分析
      • 静态单一分配 (ssa) 分析
        随着
      • 不断传播(将 if 转换为 if(3==3)
        然后
      • 常量表达式消除
        那么
      • 死代码/死分支删除

      另一方面,某些编译器在优化过程中可能计算不足,无法将其删除。

      在这种情况下,如果您在具有分支预测功能的芯片上运行代码,芯片将“获知”第一个分支已被预测,并有利地缓存(获取)该分支。但这不是编译器机制,通常不称为优化。

      【讨论】:

        【解决方案4】:

        大多数代码优化不能在运行时发生,至少不是你的意思。代码在执行期间不能更改以反映一组新的或不同的变量,这只会造成绝对的混乱。

        在运行时可以做的是通过代码选择最佳路径,但这主要是手动完成,以便创建单独的路径、早期输出、分支等以进行优化。

        这样的代码在编写时允许编译时优化,因为编译器可以检查abc 的任何可能的替代值,如果没有找到,则优化调用。然而,搜索的范围会极大地影响这是否会发生,而编译器设置也会影响到这一点。

        如果编译器只是简单地优化单个目标文件,而您的第二行位于打印部分的另一个文件中,那么它可能无法保证 abc 不会更改,因此无法保证优化这个。无论变量使用如何,这取决于您的编译器设置的激进程度以及是否允许它们丢弃死分支,或者会考虑这样做。优化大小可能比速度优化更可能删除分支,但中/高设置可能会以任何一种方式进行(如果可能的话)。

        大多数现代编译器都有一个整体程序优化选项,它将大部分优化延迟到链接器阶段。这允许它搜索整个程序,可能会发现abc 从未在其他任何地方更改或使用过,并从条件中删除变量和失败的分支。整个程序优化比针对每个对象单独优化要有效得多,因为它可以实现更准确的搜索。

        在编译器无法修剪死代码的情况下,您可以提示它(最近的构造,例如 constexpr 可以帮助解决这个问题)或自己添加优化。它可以简单到将最有可能的路径放在首位,并在 else 之前包含一个返回,从而避免 CPU 进行跳转。这种微优化不太可能是必要的,当然不是在像这样的简单示例中,if 本身已经进行了大量优化。

        【讨论】:

          【解决方案5】:

          在编译时:)

          (嗯,原则上取决于编译器等)

          【讨论】:

            【解决方案6】:

            我不太熟悉编译器如何优化代码,但是我知道从您那里的代码中,编译器可以推断出abc 永远不会被更改。这意味着它可以完全取出if 语句,只调用第一个printf 函数。

            然后在运行时,剩下的优化不多,因为代码非常简单。如果abc 发生变化,那么显然if 不能被忽略,但是,在运行时,CPU 上的分支预测之类的东西将尝试预测所采用的代码路径。这也可以被认为是一种优化形式。

            注意:我没有声称会发生这种情况,但我可以看到编译器可能会以这种方式进行优化,在积极的优化设置中。

            【讨论】:

              【解决方案7】:

              C 代码通常使用静态(也称为提前)编译进行编译。一旦离开编译器,代码就一成不变;它不能在运行时改变。

              这与使用 just-in-time compilation 的语言(例如 Java)形成对比。在那里,几乎可以在程序运行时随时进行优化。

              【讨论】:

              • C 中最接近运行时优化的是基于分析器的优化。您编译程序,然后运行它,同时使用分析器收集统计信息。然后再次编译,将这些统计信息提供给编译器。第二次编译将使用此信息生成更快的代码。
              猜你喜欢
              • 1970-01-01
              • 2012-03-08
              • 2012-11-23
              • 2015-09-27
              • 2020-07-23
              • 2022-11-23
              • 1970-01-01
              • 2017-01-30
              • 1970-01-01
              相关资源
              最近更新 更多