Table of Contents
gcc
What is GCC?
GCC (the GNU Compiler Collection) is a compiler suite that turns C, C++, Fortran, and other source languages into machine code. It is the default compiler on most Linux distributions and the reference implementation that most other C/C++ compilers (Clang included) are measured against for language conformance.
A compiler's job is to translate a high-level program into instructions the target CPU can execute, but GCC does much more than a literal translation. It performs optimisation passes that reorder, eliminate, vectorise, and inline code, decisions that can change performance by an order of magnitude without changing program behaviour. Understanding what GCC does with your source, not just how to invoke it, is central to writing fast C/C++ code.
In HPC work GCC matters twice over: as the compiler for the application code, and as the thing whose optimisation reports and generated assembly you read when a loop is slower than it should be. -O3 is a starting point, not an answer.
Install
On Debian and Ubuntu:
sudo apt install build-essential
build-essential pulls in gcc, g++, make, and the standard C library headers. On Fedora and RHEL:
sudo dnf groupinstall "Development Tools"
To check the installed version and default target:
gcc --version gcc -dumpmachine
Practice
The basic invocation compiles and links in one step:
gcc -o hello hello.c
Under the hood this runs four stages, preprocessing, compilation to assembly, assembly to object code, and linking, but gcc hides them behind a single command by default. Each stage can be inspected separately:
gcc -E hello.c -o hello.i # preprocessed source gcc -S hello.c -o hello.s # assembly gcc -c hello.c -o hello.o # object file gcc hello.o -o hello # link
For a program split across multiple files, each .c file becomes its own object file and the linker combines them:
gcc -c a.c -o a.o gcc -c b.c -o b.o gcc a.o b.o -o app
Concepts
Optimisation levels
-O0 through -O3 (and -Ofast, -Os) select a bundle of optimisation passes. -O0 is the default and disables optimisation entirely, which is why debug builds compiled without any -O flag are useful for gdb but say nothing about real performance.
gcc -O0 -g -o app app.c # debug build, unoptimised, symbols kept gcc -O3 -o app app.c # optimised build
-O2 enables most optimisations that don't risk increasing code size significantly; -O3 adds aggressive loop and vectorisation transforms on top. -Ofast additionally relaxes strict IEEE floating-point semantics, which can break numerically sensitive code that depends on exact rounding behaviour.
Vectorisation and target flags
By default GCC compiles for a conservative baseline instruction set so the binary runs on any machine of the target architecture. To let GCC use the SIMD instructions actually available on the build machine, pass -march=native:
gcc -O3 -march=native -o app app.c
-march=native is unsafe to redistribute (the binary may use instructions the deployment machine lacks) but is the right choice when building and running on the same node, which is the common case in HPC benchmarking. -fopt-info-vec reports which loops were successfully vectorised and, more usefully, which ones were not and why:
gcc -O3 -march=native -fopt-info-vec-missed -c hot_loop.c
Warnings as a correctness tool
-Wall -Wextra catch a large class of bugs (uninitialised variables, signed/unsigned comparison mismatches, unused results) before they become runtime failures. -Wall is not “all warnings” despite the name; it's a curated subset considered low false-positive.
gcc -Wall -Wextra -Wpedantic -o app app.c
-Wpedantic additionally flags non-standard GNU extensions, useful when portability to another compiler matters.
Generated assembly
Reading what GCC actually emitted is the ground truth when a “should be fast” loop isn't. -S combined with -fverbose-asm annotates the assembly with the corresponding source variable names:
gcc -O3 -march=native -S -fverbose-asm hot_loop.c -o hot_loop.s
Cross-referencing this with a perf annotate session on the compiled binary is the standard loop to close when chasing a specific instruction-level performance problem.
