【问题标题】:Optimization switches - what do they really do? [closed]优化开关——它们的真正作用是什么? [关闭]
【发布时间】:2012-09-07 11:05:50
【问题描述】:

可能每个人都使用某种优化开关(在 gcc 的情况下,最常见的是 -O2 我相信)。

但是 gcc(以及 VS、Clang 等其他编译器)真正在存在这些选项的情况下会做什么?

当然没有明确的答案,因为它很大程度上取决于平台、编译器版本等。 但是,如果可能的话,我想收集一套“经验法则”。 我什么时候应该考虑一些技巧来加速代码,什么时候应该把工作留给编译器?

例如,编译器会走多远(有点人为......) 案例,针对不同的优化级别:

1) sin(3.141592) // 会在编译时进行评估,还是我应该考虑一个查找表来加快计算速度?

2) int a = 0; a = exp(18), cos(1.57), 2; // 编译器是否会计算 exp 和 cos,尽管不需要,因为表达式的值等于 2?

3)

for (size_t i = 0; i < 10; ++i) {
  int a = 10 + i;
}

// 编译器会跳过整个循环,因为它没有可见的副作用吗?

也许你可以想想其他例子。

【问题讨论】:

  • @LuboAntonov:我认为这是一个完美的问题。他希望客观地回答有关编译器优化的三个问题。除了观察生成的程序集外,没有一个明确的方法可以知道您是否对这一切都很陌生。
  • 最好的建议是使用 -S 选项(对于 gcc)将代码编译为汇编语言,然后查看它生成的代码。当然,您需要学习汇编语言的基础知识,但并不难理解。
  • 你读过 gcc 的文档吗?它非常详细地解释了 -ON 启用的功能,并且这些选项有时已经在手册页中进行了非常详细的解释。编译器版本和/或编译器编译方式的优化也得到了改善,因此没有人可以预测编译器通常会做什么,您必须查看生成的汇编程序。 gcc.godbolt.org 是一个很好的工具。
  • @Josh 这是一个开放式问题,甚至 OP 也承认每个编译器的答案都会有所不同,征求意见,他想基本上开始一个 wiki 讨论。我认为这涉及到问题在这里结束的所有原因。
  • @MatthieuM。 - 问题过于宽泛:一个完整​​的答案可能会写满一本书或几本书。

标签: c++ visual-c++ gcc clang


【解决方案1】:

如果您想知道编译器的作用,最好的办法是查看编译器文档。对于优化,您可以查看 LLVM's Analysis and Transform Passes 示例。

1) sin(3.141592) // 是否会在编译时进行评估?

大概吧。 IEEE 浮点计算有非常精确的语义。顺便说一句,如果您在运行时更改处理器标志,这可能会令人惊讶。

2) int a = 0; a = exp(18), cos(1.57), 2;

这取决于:

  • expcos 函数是否内联
  • 如果不是,它们是否正确注释(因此编译器知道它们没有副作用)

对于取自 C 或 C++ 标准库的函数,它们应该被正确识别/注释。

至于计算的消除:

  • -adce: 积极的死代码消除
  • -dce: 死代码消除
  • -die: 死指令消除
  • -dse: 死店消除

编译器喜欢寻找无用的代码:)

3)

实际上类似于2)。不使用 store 的结果,表达式为无副作用。

  • -loop-deletion: 删除死循环

最后:什么不对编译器进行测试?

#include <math.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
  double d = sin(3.141592);
  printf("%f", d);

  int a = 0; a = (exp(18), cos(1.57), 2); /* need parentheses here */
  printf("%d", a);

  for (size_t i = 0; i < 10; ++i) {
    int a = 10 + i;
  }

  return 0;
}

Clang 在编译过程中已经尝试提供帮助:

12814_0.c:8:28: warning: expression result unused [-Wunused-value]
  int a = 0; a = (exp(18), cos(1.57), 2);
                           ^~~ ~~~~
12814_0.c:12:9: warning: unused variable 'a' [-Wunused-variable]
    int a = 10 + i;
        ^

以及发出的代码(LLVM IR):

@.str = private unnamed_addr constant [3 x i8] c"%f\00", align 1
@.str1 = private unnamed_addr constant [3 x i8] c"%d\00", align 1

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind uwtable {
  %1 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str, i64 0, i64 0), double 0x3EA5EE4B2791A46F) nounwind
  %2 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([3 x i8]* @.str1, i64 0, i64 0), i32 2) nounwind
  ret i32 0
}

我们注意到:

  • 正如预测的那样,sin 计算已在编译时解决
  • 正如预测的那样,expcos 已被完全剥离。
  • 正如预测的那样,循环也已被剥离。

如果您想更深入地研究编译器优化,我建议您:

  • 学习阅读 IR(这非常容易,真的,组装起来要简单得多)
  • 使用 LLVM 试用页面来测试您的假设

【讨论】:

    【解决方案2】:

    编译器有许多优化通道。每个优化过程都负责一些小的优化。例如,您可能有一个在编译时计算算术表达式的过程(例如,您可以将 5MB 表示为 5 * (1024*1024) 而不会受到惩罚)。另一个传递内联函数。另一个搜索无法访问的代码并将其杀死。以此类推。

    然后编译器的开发人员决定他们希望以何种顺序执行这些传递中的哪些。例如,假设您有以下代码:

    int foo(int a, int b) {
      return a + b;
    }
    
    void bar() {
      if (foo(1, 2) > 5)
        std::cout << "foo is large\n";
    }
    

    如果您对此运行死代码消除,则不会发生任何事情。同样,如果您运行表达式缩减,则不会发生任何事情。但是内联器可能会认为 foo 小到可以内联,所以它用函数体替换 bar 中的调用,替换参数:

    void bar() {
      if (1 + 2 > 5)
        std::cout << "foo is large\n";
    }
    

    如果你现在运行表达式缩减,它会先判断1+2为3,然后判断3>5为假。所以你得到:

    void bar() {
      if (false)
        std::cout << "foo is large\n";
    }
    

    现在死代码消除会看到一个 if(false) 并杀死它,所以结果是:

    void bar() {
    }
    

    但是现在 bar 突然变得非常小了,之前它更大更复杂。因此,如果您再次运行内联程序,它将能够将 bar 内联到其调用者中。这可能会带来更多优化机会,等等。

    对于编译器开发人员来说,这是编译时间和生成的代码质量之间的权衡。他们根据启发式、测试和经验决定要运行的优化器序列。但是由于一种尺寸并不适合所有尺寸,因此它们会暴露一些旋钮来调整它。 gcc 和 clang 的主要旋钮是 -O 选项系列。 -O1 运行一个简短的优化器列表; -O3 运行更长的列表,其中包含更昂贵的优化器,并且更频繁地重复传递。

    除了决定运行哪些优化器之外,这些选项还可以调整各种通道使用的内部启发式方法。例如,内联器通常有很多参数来决定何时值得内联一个函数。通过 -O3,只要有可能提高性能,这些参数就会更倾向于内联函数;传递 -Os,参数只会导致非常小的函数(或可证明只调用一次的函数)被内联,因为其他任何东西都会增加可执行文件的大小。

    【讨论】:

      【解决方案3】:

      编译器会进行各种你想不到的优化。尤其是 C++ 编译器。

      他们会做一些事情,比如展开循环、使函数内联、消除死代码、用一条指令替换多条指令等等。

      我可以给出的一条建议是:在 C/C++ 编译器中,您可以相信它们会执行很多优化。

      看看 [1]。

      [1]http://en.wikipedia.org/wiki/Compiler_optimization

      【讨论】:

        猜你喜欢
        • 2021-12-26
        • 2011-12-17
        • 2012-11-19
        • 2011-09-03
        • 2010-09-06
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 2014-06-29
        相关资源
        最近更新 更多