当前位置: 首页 > 知识库问答 >
问题:

为什么用-Os编译会使这个函数变大?

莫骞仕
2023-03-14

考虑这个函数:

long foo(long x) {
    return 5*x + 6;
}

当我用带有< code>-O3(或< code>-O2或< code>-O1)的x86-64 gcc 8.2编译它时,它会编译成这样:

foo:
  leaq 6(%rdi,%rdi,4), %rax  # 5 bytes: 48 8d 44 bf 06
  ret                        # 1 byte:  c3

当我使用-Os时,它编译成这样:

foo:
  leaq (%rdi,%rdi,4), %rax   # 4 bytes: 48 8d 04 bf
  addq $6, %rax              # 4 bytes: 48 83 c0 06
  ret                        # 1 byte:  c3

后者长3个字节。-Os不是应该产生尽可能小的代码吗,即使更大的代码会更有效率?为什么这里似乎发生了相反的情况?

戈德波特:https://godbolt.org/z/jzNquk

共有1个答案

家弘业
2023-03-14

与使用<code>-O1</code>、<code>-O2</code>和<code>-O3</code>选项(“优化速度”)生成的代码相比,<code>-Os</code>(“优化大小”)预计会生成更紧凑的代码,但正如@Robert Harvey所评论的,确实没有这样的保证。

优化编译是一个非常复杂和微妙的过程。它由几十个不同的优化阶段组成,这些阶段通常是串行执行的:每个优化阶段都在程序树表示上进行工作,并为下一阶段做好准备。在优化过程中,一个阶段中做出的每一个决策都可能会对未来的优化产生影响,并且通道可能以非平凡的方式相互作用,这可能很难预测。编译器使用不同的启发式方法来生成最优化的代码,但在某些情况下,这些启发式方法并不适用,如本例所示。

在本例中,似乎一切都按预期开始了。<code>-Os</code>生成了更紧凑的中间代码,但这在以后会发生变化。GCC执行的第一个阶段是扩展阶段,它将GCC的高级树表示(称为GIMPLE)转换为低级RTL表示。它生成与以下类似的RTL代码:

O3:

  1. tmp1

操作系统:

  1. tmp

到目前为止,一切顺利-<code>-操作系统</code>获胜。但之后,大约15个阶段之后,执行合并阶段,该阶段尝试将一系列指令合并为一条指令。对于<code>-O3-Os,Combine做得不太好,无法进一步折叠代码。从这一点来看,进一步优化后代码不会有太大变化。

为了回答确切的问题 - 为什么GCC这样做(生成它在使用-O3扩展期间所做的代码,以及为什么Combine在-Os中做得不好),必须检查GCC代码并找出哪些GCC参数是有影响力的,以及前面的优化阶段做出的决策。

但是,问题是,虽然GCC在这个例子中表现不佳,但它可能是大多数其他例子的最佳选择。这是一个微妙的权衡问题——对于编译器编写者来说不是一件容易的工作!

这可能无法完全回答问题,但希望它能提供一些有用的背景知识。如果您有兴趣在每个优化阶段检查 GCC 的输出,则可以添加 -da 编译标志(它将为每个阶段生成带注释的树转储)和 -dP 标志(它将树注释添加到生成的程序集输出)和 -S

 类似资料:
  • 奇怪的是,标记为“OK”的行编译得很好,但标记为“Error”的行失败了。它们看起来基本上是一样的。

  • 今天我遇到了一个有趣的问题,是我自己打错的。我创建了一个lambda,它接受了对结构的引用,并错误地将其设置为按值接收参数的std::函数。 这里有一个更简洁的版本: 使用godbolt检查显示,使用MSVC编译成功,但对于Clang和GCC都失败了。 这是MSVC编译器中的bug吗?

  • 问题内容: 如果你给 它没有编译,但是带有花括号的相同代码是: 有什么解释? 问题答案: 基本上,变量声明只能在块中声明。 查看 Java语言规范中“语句”的语法 -它包括Block,但不包括LocalVariableDeclarationStatement- 后者是block语法的一部分。 这实际上是实用主义的问题:如果没有括号,则只能使用一个语句。如果没有后续语句,则声明变量是没有意义的,因为

  • 问题内容: 来自C语言的Go语言最值得注意的方面之一是,如果在其中声明了一个未使用的变量,编译器将不会编译您的程序。那么,如果在函数中声明了一个未使用的参数,那么为什么要构建此程序呢? 问题答案: 没有正式的原因,但是在golang-nuts上给出的原因是: 未使用的变量始终是编程错误,而编写不使用其所有参数的函数是很常见的。 可以将这些参数保留为未命名(使用_),但这可能会与诸如 func fo

  • 问题内容: 这段代码使我凝视了几分钟: (这里的第137行) 我以前从未见过,而且我也不知道Java有一个“ loop”关键字(NetBeans甚至没有像关键字一样给它上色),并且它在JDK 6中可以很好地编译。 有什么解释? 问题答案: 它不是一个keyword,而是一个label。 用法:

  • 预计此函数将无法typeCheck。然而,没有解释发生这种情况的原因。在GHCI中试用时,我得到了以下输出: 为什么会出现这种情况?