To be pedantic, there are 8 different valid -O options you can give to gcc, though there are some that mean the same thing.

The original version of this answer stated there were 7 options. GCC has since added -Og to bring the total to 8.

From the man page:

  • -O (Same as -O1)
  • -O0 (do no optimize, the default if no optimization level is specified)
  • -O1 (optimize minimally, favoring compilation time)
  • -O2 (optimize more, without speed/size tradeoff)
  • -O3 (optimize even more, favoring speed)
  • -Ofast (optimize very aggressively to the point of breaking standard compliance, favoring speed. May change program behavior)
  • -Og (Optimize debugging experience. -Og enables optimizations that do not interfere with debugging. It should be the optimization level of choice for the standard edit-compile-debug cycle, offering a reasonable level of optimization while maintaining fast compilation and a good debugging experience.)
  • -Os (Optimize for size. -Os enables all -O2 optimizations that do not typically increase code size. It also performs further optimizations designed to reduce code size. -Os disables the following optimization flags: -falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays -ftree-vect-loop-version)

There may also be platform specific optimizations, as @pauldoo notes, OS X has -Oz.

Answer from Glen on Stack Overflow
🌐
GNU
gcc.gnu.org › onlinedocs › gcc › Optimize-Options.html
Optimize Options (Using the GNU Compiler Collection (GCC))
-O3 turns on all optimizations specified by -O2 and also turns on the following optimization flags: -fgcse-after-reload -fipa-cp-clone -floop-interchange -floop-unroll-and-jam -fpeel-loops -fpredictive-commoning -fsplit-loops -fsplit-paths -ftree-loop-distribution -ftree-partial-pre -funswitch-loops -fvect-cost-model=dynamic -fversion-loops-for-strides ... Reduce compilation time and make debugging produce the expected results. This is the default. At -O0, GCC completely disables most optimization passes; they are not run even if you explicitly enable them on the command line, or are listed by -Q --help=optimizers as being enabled by default.
Top answer
1 of 4
221

To be pedantic, there are 8 different valid -O options you can give to gcc, though there are some that mean the same thing.

The original version of this answer stated there were 7 options. GCC has since added -Og to bring the total to 8.

From the man page:

  • -O (Same as -O1)
  • -O0 (do no optimize, the default if no optimization level is specified)
  • -O1 (optimize minimally, favoring compilation time)
  • -O2 (optimize more, without speed/size tradeoff)
  • -O3 (optimize even more, favoring speed)
  • -Ofast (optimize very aggressively to the point of breaking standard compliance, favoring speed. May change program behavior)
  • -Og (Optimize debugging experience. -Og enables optimizations that do not interfere with debugging. It should be the optimization level of choice for the standard edit-compile-debug cycle, offering a reasonable level of optimization while maintaining fast compilation and a good debugging experience.)
  • -Os (Optimize for size. -Os enables all -O2 optimizations that do not typically increase code size. It also performs further optimizations designed to reduce code size. -Os disables the following optimization flags: -falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays -ftree-vect-loop-version)

There may also be platform specific optimizations, as @pauldoo notes, OS X has -Oz.

2 of 4
80

Let's interpret the source code of GCC 5.1

We'll try to understand what happens on -O100, since it is not clear on the man page.

We shall conclude that:

  • anything above -O3 up to INT_MAX is the same as -O3, but that could easily change in the future, so don't rely on it.
  • GCC 5.1 runs undefined behavior if you enter integers larger than INT_MAX.
  • the argument can only have digits, or it fails gracefully. In particular, this excludes negative integers like -O-1

Focus on subprograms

First remember that GCC is just a driver for cpp, as, cc1, collect2. A quick ./XXX --help says that only collect2 and cc1 take -O, so let's focus on them.

And:

gcc -v -O100 main.c |& grep 100

gives:

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.

so -O was forwarded to both cc1 and collect2.

O in common.opt

common.opt is a GCC specific CLI option description format described in the internals documentation and translated to C by opth-gen.awk and optc-gen.awk.

It contains the following interesting lines:

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

which specify all the O options. Note how -O<n> is in a separate family from the other Os, Ofast and Og.

When we build, this generates a options.h file that contains:

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

As a bonus, while we are grepping for \bO\n inside common.opt we notice the lines:

-optimize
Common Alias(O)

which teaches us that --optimize (double dash because it starts with a dash -optimize on the .opt file) is an undocumented alias for -O which can be used as --optimize=3!

Where OPT_O is used

Now we grep:

git grep -E '\bOPT_O\b'

which points us to two files:

  • opts.c
  • lto-wrapper.c

Let's first track down opts.c

opts.c:default_options_optimization

All opts.c usages happen inside: default_options_optimization.

We grep backtrack to see who calls this function, and we see that the only code path is:

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

and main.c is the entry point of cc1. Good!

The first part of this function:

  • does integral_argument which calls atoi on the string corresponding to OPT_O to parse the input argument
  • stores the value inside opts->x_optimize where opts is a struct gcc_opts.

struct gcc_opts

After grepping in vain, we notice that this struct is also generated at options.h:

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

where x_optimize comes from the lines:

Variable
int optimize

present in common.opt, and that options.c:

struct gcc_options global_options;

so we guess that this is what contains the entire configuration global state, and int x_optimize is the optimization value.

255 is an internal maximum

in opts.c:integral_argument, atoi is applied to the input argument, so INT_MAX is an upper bound. And if you put anything larger, it seem that GCC runs C undefined behaviour. Ouch?

integral_argument also thinly wraps atoi and rejects the argument if any character is not a digit. So negative values fail gracefully.

Back to opts.c:default_options_optimization, we see the line:

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

so that the optimization level is truncated to 255. While reading opth-gen.awk I had come across:

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

and on the generated options.h:

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

which explains why the truncation: the options must also be forwarded to cl_optimization, which uses a char to save space. So 255 is an internal maximum actually.

opts.c:maybe_default_options

Back to opts.c:default_options_optimization, we come across maybe_default_options which sounds interesting. We enter it, and then maybe_default_option where we reach a big switch:

switch (default_opt->levels)
  {

  [...]

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

  [...]

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

There are no >= 4 checks, which indicates that 3 is the largest possible.

Then we search for the definition of OPT_LEVELS_3_PLUS in common-target.h:

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.  */
};

Ha! This is a strong indicator that there are only 3 levels.

opts.c:default_options_table

opt_levels is so interesting, that we grep OPT_LEVELS_3_PLUS, and come across 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 },
    [...]
}

so this is where the -On to specific optimization mapping mentioned in the docs is encoded. Nice!

Assure that there are no more uses for x_optimize

The main usage of x_optimize was to set other specific optimization options like -fdefer_pop as documented on the man page. Are there any more?

We grep, and find a few more. The number is small, and upon manual inspection we see that every usage only does at most a x_optimize >= 3, so our conclusion holds.

lto-wrapper.c

Now we go for the second occurrence of OPT_O, which was in lto-wrapper.c.

LTO means Link Time Optimization, which as the name suggests is going to need an -O option, and will be linked to collec2 (which is basically a linker).

In fact, the first line of lto-wrapper.c says:

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

In this file, the OPT_O occurrences seems to only normalize the value of O to pass it forward, so we should be fine.

🌐
Gentoo Wiki
wiki.gentoo.org › wiki › GCC_optimization
GCC optimization - Gentoo wiki
When using a compiler version prior to GCC 12, in order to take full advantage of AVX YMM registers, the -ftree-vectorize or -O3 options should be used as well[1]. Even though the CHOST variable in /etc/portage/make.conf specifies the general architecture used, -march should still be used so that programs can be optimized for the system specific processor. x86 and x86-64 CPUs (among others) should make use of the -march flag...
🌐
RapidTables
rapidtables.com › code › linux › gcc › gcc-o.html
gcc -o / -O option flags (output / optimization)
gcc -O sets the compiler's optimization level. gcc -o option flag · gcc -O option flag · Write the build output to an output file. $ gcc [options] [source files] [object files] -o output file ·
🌐
Interrupt
interrupt.memfault.com › blog › code-size-optimization-gcc-flags
Code Size Optimization: GCC Compiler Flags | Interrupt
August 20, 2019 - To enable dead code optimization ... are not called by anything. This is achieved with the -ffunction-sections compile-time flag and the -gc-sections link-time flag....
🌐
Mithilesh's Blog
esymith.hashnode.dev › a-comprehensive-guide-to-gcc-flags
A Comprehensive Guide to GCC Flags
April 14, 2024 - Some commonly used code generation flags include: -march, -mtune: These flags specify the target CPU architecture and tuning parameters for code generation, optimizing the generated code for specific hardware platforms.
🌐
Oracle
docs.oracle.com › en › operating-systems › oracle-linux › 6 › porting › ch04s03.html
4.3 Optimizing gcc Compilation
The compiler optimizes to reduce the size of the binary instead of execution speed. If you do not specify an optimization option, gcc attempts to reduce the compilation time and to make debugging always yield the result expected from reading the source code.
🌐
Medium
medium.com › @guannan.shen.ai › compiler-optimizations-46db19221947
Compiler Optimizations. Low Level Optimization Options: GCC… | by Guannan Shen | Medium
December 8, 2021 - To see the individual features enabled by a particular -O flag, or combination of -O and feature flags, use the -Q --help=optimizers flags, which will query the optimization feature list. For example, to see all of the optimizations enabled/disabled at -O2, use this command: ... The GCC documentation — both the man page and even more so the online manuals — have good information about the 200+ optimization features of the GCC compilers.
Find elsewhere
🌐
Boston University
bu.edu › tech › support › research › software-and-programming › programming › compilers › gcc-compiler-flags
GNU and LLVM Compiler Flags : TechWeb : Boston University
Most codes will be well-optimized with the -O2 or -O3 flags plus the -msse4.2 flag. Programs that involve intensive floating-point calculations inside of loops can additionally be compiled with the -xarch flag. For maximum cross-compatibility across the SCC compute nodes and probable highest ...
🌐
SPEC
spec.org › cpu2017 › flags › gcc.html
GNU Compiler Collection Flags
Enables a range of optimizations that provide faster, though sometimes less precise, mathematical operations. ... Tells GCC to use the GNU semantics for "inline" functions, that is, the behavior prior to the C99 standard. This switch may resolve duplicate symbol errors, as noted in the 502.gcc_r ...
🌐
GNU
gcc.gnu.org › onlinedocs › gcc-3.3.4 › gcc › Optimize-Options.html
Using the GNU Compiler Collection (GCC)
GCC performs nearly all supported ... to -O, this option increases both compilation time and the performance of the generated code. -O2 turns on all optimization flags ......
🌐
Bookmarklet Maker
caiorss.github.io › C-Cpp-Notes › compiler-flags-options.html
CPP/C++ Compiler Flags and Options
Problem: Slower compile-time and large binary size. More function inlining; loop vectorization and SIMD instructions. ... Enable (-O2), but disable some optimizations flags in order to reduce object-code size.
🌐
Pronod's Blog
data-intelligence.hashnode.dev › maximizing-c-program-performance-a-comprehensive-guide-to-gcc-compiler-optimization
Unlocking Peak Performance: GCC Compiler Optimization for C Programs
November 1, 2023 - Common Optimization Options: It briefly discusses common optimization options, such as -Os (optimizing for size), -Ofast (aggressive optimizations), and other flags like -march=native and -ffast-math.
🌐
Stanford
web.stanford.edu › class › archive › cs › cs107 › cs107.1202 › resources › gcc
CS107 Compiling C Programs with GCC
gcc has the ability to optimize your code for various situations. -O or -O1: Optimize. Optimizing compilation takes somewhat more time, and a lot more memory for a large function. With -O, the compiler tries to reduce code size and execution time, without performing any optimizations that take ...
🌐
Stanford
web.stanford.edu › class › archive › cs › cs107 › cs107.1186 › unixref › topics › gcc
gcc (how to compile c programs)
One of the most common flags is the "optimization level" flag, -O (uppercase 'o'). gcc has the ability to optimize your code for various situations. See the man page for gcc for more details (or see here, but here are the basics: -O or -O1: Optimize. Optimizing compilation takes somewhat more ...
🌐
Reddit
reddit.com › r/learnprogramming › question regarding gcc optimization flags
r/learnprogramming on Reddit: Question regarding GCC optimization flags
August 1, 2019 -

Hi everyone!

I am currently doing an online course, and I am currently stuck on a part where one should try out a few different optimization flags (e.g. -O0, -O3 and -Os) when compiling a c program. The application I am compiling is a gtk application utilizing a few different shared libraries.

To my question: When comparing the resulting sizes of the -O0 and -Os binaries, it appears that the -O0 binary is smaller, even though -Os should optimize for binary size (why i think it should be smaller). Is this something that is possible or have I made an error somewhere? The compilation is currently done via Eclipse (which is required for the course).

Best regards

quadamp