有多少个GCC优化级别?

136

有多少个GCC优化级别?

我尝试了gcc -O1、gcc -O2、gcc -O3和gcc -O4。

如果我使用一个非常大的数字,它就不起作用。

但是,我已经尝试过

gcc -O100

并且它被编译了。

有多少个优化级别?


13
你在看哪个版本的FM?即使在 Cygwin 上使用 man gcc(大约 12000 行),你也可以搜索 -O,找到以下回答中提到的所有内容,以及更多的内容。 - Jens
1
阅读源代码后,我不同意@minmaxavg的观点:任何大于3的数都与3相同(只要它没有int溢出)。请参见我的答案 - Ciro Santilli OurBigBook.com
2
实际上,GCC还有许多其他标志可以微调优化。-fomit-stack-pointer将更改生成的代码。 - Basile Starynkevitch
4个回答

203

严谨来说,您可以给gcc提供8种不同的有效-O选项,尽管有些选项具有相同的含义。

这个答案的原始版本声称只有7个选项。GCC后来添加了-Og,使总数达到8个。

来源于man页面:

  • -O (与-O1相同)
  • -O0 (不进行优化,如果未指定优化级别,则为默认值)
  • -O1 (最小化优化)
  • -O2 (更多优化)
  • -O3 (更加优化)
  • -Ofast (非常激进的优化,甚至会破坏标准兼容性)
  • -Og (优化调试体验。-Og启用不会干扰调试的优化。它应该是标准编辑-编译-调试周期的优化级别,可以在保持快速编译和良好调试体验的同时提供合理的优化水平。)
  • -Os (针对大小进行优化。 -Os 启用所有不会增加代码大小的 -O2 优化。它还执行旨在减少代码大小的进一步优化。 -Os 禁用以下优化标志:-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays -ftree-vect-loop-version

还可能有特定于平台的优化,如 @pauldoo 所述,OS X 有 -Oz


31
如果您正在 Mac OS X 上进行开发,还有一个额外的 -Oz 设置,它比 -Os 更积极地优化尺寸:http://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Optimize-Options.html - pauldoo
9
注意:尽管名称暗示O3可能比O2更好,但并非总是如此。请都试一下。 - johan d
2
@pauldoo 404页面,替换为archive.org。 - noɥʇʎԀʎzɐɹƆ
1
@pauldoo 工作链接 https://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Optimize-Options.html - Max MacLeod
调用“Os”进行大小优化在我看来是有误导性的,因为它仍然主要针对速度进行优化,但它只是跳过或更改某些优化,否则可能会导致代码大小增加。您在文本中解释得很好,只是指出了我通常的一个烦恼,即说它意味着“优化大小”,暗示它是优化速度的相反。应该永远不使用“O0”,因为它会生成荒谬的代码,就像来自1970年代编译器的东西一样,现在基本上任何使用它的理由都已经不存在了,因为现在有了“Og”。 - thomasrutter
显示剩余3条评论

74

让我们解释GCC 5.1的源代码

我们将尝试理解在-O100上发生了什么,因为在手册上不太清楚。

我们得出结论:

  • -O3INT_MAX以上的任何内容与-O3相同,但未来可能会轻松更改,因此不要依赖它。
  • 如果您输入大于INT_MAX的整数,则GCC 5.1会运行未定义的行为。
  • 参数只能包含数字,否则会失败。特别是,这不包括负整数,如-O-1

关注子程序

首先请记住,GCC只是cppascc1collect2的前端。快速运行 ./XXX --help 表明只有collect2cc1采用 -O ,因此让我们专注于它们。

而且:

gcc -v -O100 main.c |& grep 100
给出:
COLLECT_GCC_OPTIONS='-O100' '-v' '-mtune=generic' '-march=x86-64'
/usr/local/libexec/gcc/x86_64-unknown-linux-gnu/5.1.0/cc1 [[noise]] hello_world.c -O100 -o /tmp/ccetECB5.

-O参数被转发到了cc1collect2

common.opt中的-O选项

common.opt 是一种GCC特定的CLI选项描述格式,由opth-gen.awkoptc-gen.awk翻译为C语言,并在内部文档中进行了说明。

它包含以下有趣的行:

O
Common JoinedOrMissing Optimization
-O<number>  Set optimization level to <number>

Os
Common Optimization
Optimize for space rather than speed

Ofast
Common Optimization
Optimize for speed disregarding exact standards compliance

Og
Common Optimization
Optimize for debugging experience rather than speed or size

这些指定所有O选项。请注意,-O<n>与其他OsOfastOg不属于同一族群。

构建时,会生成一个包含以下内容的options.h文件:

OPT_O = 139,                               /* -O */
OPT_Ofast = 140,                           /* -Ofast */
OPT_Og = 141,                              /* -Og */
OPT_Os = 142,                              /* -Os */

作为额外收获,当我们在common.opt中搜索\bO\n时,我们注意到以下几行:

-optimize
Common Alias(O)

这告诉我们,--optimize(由于以破折号 -optimize 开始,所以使用双破折号)是 -O 的未记录别名,并且可以使用 --optimize=3

OPT_O 的使用

现在我们使用 grep:

git grep -E '\bOPT_O\b'

这指向两个文件:

首先我们来查找opts.c

opts.c:default_options_optimization

opts.c 的所有用法都在default_options_optimization函数里。

我们使用grep回溯调用此函数的代码,我们发现唯一的代码路径是:

  • main.c:main
  • toplev.c:toplev::main
  • opts-global.c:decode_opts
  • opts.c:default_options_optimization

main.ccc1的入口点。很好!

此函数的第一部分:

  • 执行 integral_argument,它对与 OPT_O 相应的字符串调用 atoi 来解析输入参数
  • 将该值存储在 opts->x_optimize 中,其中 opts 是一个 struct gcc_opts

struct gcc_opts

在枯燥的搜索中,我们注意到该struct也是在options.h中生成的:

struct gcc_options {
    int x_optimize;
    [...]
}

其中x_optimize来自以下代码行:

Variable
int optimize

出现在common.opt中,并且出现在options.c中:

struct gcc_options global_options;

我们猜测这就是包含整个配置全局状态的内容,int x_optimize 是优化值。

255 是一个内部最大值

opts.c:integral_argument 中,将输入参数应用于 atoi,因此 INT_MAX 是上限。如果放入任何较大的值,似乎GCC会运行C未定义的行为。 疼?

integral_argument 也是轻薄地包装了 atoi,并在任何字符不是数字时拒绝该参数。因此负值会正常失败。

回到 opts.c:default_options_optimization,我们看到一行:

if ((unsigned int) opts->x_optimize > 255)
  opts->x_optimize = 255;

这样可以将优化级别截断为255。在阅读 opth-gen.awk文件时,我发现:

# All of the optimization switches gathered together so they can be saved and restored.
# This will allow attribute((cold)) to turn on space optimization.

并且在生成的 options.h 上:

struct GTY(()) cl_optimization
{
  unsigned char x_optimize;

这就解释了为什么要截断:选项也必须转发到cl_optimization,它使用一个char来节省空间。因此255实际上是一个内部最大值。

opts.c:maybe_default_options

回到opts.c:default_options_optimization,我们遇到了听起来有趣的maybe_default_options。我们进入它,然后到达maybe_default_option,在那里我们看到一个大开关:

switch (default_opt->levels)
  {

  [...]

  case OPT_LEVELS_1_PLUS:
    enabled = (level >= 1);
    break;

  [...]

  case OPT_LEVELS_3_PLUS:
    enabled = (level >= 3);
    break;

没有>= 4的检查,这说明3是最大可能的。

然后我们在common-target.h中搜索OPT_LEVELS_3_PLUS的定义:

enum opt_levels
{
  OPT_LEVELS_NONE, /* No levels (mark end of array).  */
  OPT_LEVELS_ALL, /* All levels (used by targets to disable options
                     enabled in target-independent code).  */
  OPT_LEVELS_0_ONLY, /* -O0 only.  */
  OPT_LEVELS_1_PLUS, /* -O1 and above, including -Os and -Og.  */
  OPT_LEVELS_1_PLUS_SPEED_ONLY, /* -O1 and above, but not -Os or -Og.  */
  OPT_LEVELS_1_PLUS_NOT_DEBUG, /* -O1 and above, but not -Og.  */
  OPT_LEVELS_2_PLUS, /* -O2 and above, including -Os.  */
  OPT_LEVELS_2_PLUS_SPEED_ONLY, /* -O2 and above, but not -Os or -Og.  */
  OPT_LEVELS_3_PLUS, /* -O3 and above.  */
  OPT_LEVELS_3_PLUS_AND_SIZE, /* -O3 and above and -Os.  */
  OPT_LEVELS_SIZE, /* -Os only.  */
  OPT_LEVELS_FAST /* -Ofast only.  */
};

哈!这是只有三个级别的一个强烈指示。

opts.c:default_options_table

opt_levels非常有趣,我们搜索 OPT_LEVELS_3_PLUS,并找到了 opts.c:default_options_table:

static const struct default_options default_options_table[] = {
    /* -O1 optimizations.  */
    { OPT_LEVELS_1_PLUS, OPT_fdefer_pop, NULL, 1 },
    [...]

    /* -O3 optimizations.  */
    { OPT_LEVELS_3_PLUS, OPT_ftree_loop_distribute_patterns, NULL, 1 },
    [...]
}

所以这就是在文档中提到的-On特定优化映射被编码的地方。太好了!

确保没有其他用途使用x_optimize

x_optimize的主要用途是设置其他特定优化选项,如man页上所述的-fdefer_pop。还有其他用途吗?

我们使用grep并发现还有几个用法。数目很少,通过手动检查,我们看到每个使用只做了最多x_optimize >= 3,因此我们的结论成立。

lto-wrapper.c

现在我们来到第二处出现OPT_O的地方,即lto-wrapper.c

LTO表示链接时优化,顾名思义将需要一个-O选项,并且将链接到collec2(基本上是一个链接器)。

实际上,lto-wrapper.c的第一行说:

/* Wrapper to call lto.  Used by collect2 and the linker plugin.

在这个文件中,OPT_O 的出现似乎只是将 O 的值规范化以便向前传递,所以我们应该没问题。


5
非常详细的回答,印象深刻!GCC底层实现原理。 - pmor

44

共有七个不同的级别:

  • -O0(默认值):没有优化。

  • -O-O1(相同):进行优化,但不花费太多时间。

  • -O2:更积极地进行优化。

  • -O3:最积极地进行优化。

  • -Ofast:等同于 -O3 -ffast-math-ffast-math 触发非标准兼容的浮点数优化。这使编译器可以假装浮点数是无限精确的,并且代数运算遵循实数代数的标准规则。它还告诉编译器告诉硬件将非规格化数值强制处理为零,并将非规格化数值视为零,至少在一些处理器上,包括 x86 和 x86-64。非规格化数值会触发许多 FPUs 上的慢速路径,因此将其视为零(不会触发慢速路径)可以大大提高性能。

  • -Os:优化代码大小。这实际上在某些情况下可以提高速度,因为它可以改善指令高速缓存行为。

  • -Og:进行优化,但不干扰调试。这使得调试构建具有非尴尬的性能,旨在取代 -O0 用于调试构建。

还有其他选项没有被任何一个选项启用,并且必须单独启用。也可以使用优化选项,但禁用这种优化启用的特定标志。

有关更多信息,请参见GCC网站。


事实上,为了对其他答案公平,当那些答案被撰写时,-Ofast和-Og都不存在。 - janneb
那么为什么-O100可以编译呢? - einpoklum
3
因为 GCC 将所有高于 -O3 的内容视为等同于 -O3。 - Demi
不幸的是,即使使用 -Og 选项,在调试器中仍会出现大量的 <optimized out>。单步调试仍然会随机跳转。在我看来,这毫无用处。 - doug65536

3
四个级别 (0-3): 参考GCC 4.4.2 手册。更高级别只是 -O3,但在某些时候可能会超出变量大小限制。

我已经查阅了源代码 在我的回答中,并且同意你的观点。更严谨来说,GCC 似乎依赖于 atoi 的未定义行为,然后是一个 255 的内部限制。 - Ciro Santilli OurBigBook.com
7
请考虑删除你的回答,因为它(至少目前)是不正确的。 - einpoklum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接