Compilation for Dummies: Compilers, Flags, and Options – How to Find Your Way Around?

2/19/2024

Part 1

“It compiles, so it works.” Admit it, we’ve all heard (or said) this in engineering school. But if we think about it more closely, do we really understand what this compilation step is all about?

For those lucky enough to answer yes, don’t leave just yet. Beyond a back-to-basics refresher on compilers, we’ll go a bit further with a breakdown of research and studies comparing the performance of different compilers, the scheduling of flags, and the various compilation options. For the rest—don’t worry, your secret is safe with us.

Compiler? What is it?

Sorry to disappoint, but no, gcc is not the compiler. We won’t list them all here, but the main point is to understand what a compiler is used for. In fact, it’s a bit like a translator.

As a developer, it’s much easier to read code written in a high-level language (and, if it’s well written, even understand it just by reading) rather than assembly code or, worse, a series of zeros and ones (machine code). Your compiler transforms the code you’ve written—in whatever language you choose—into another language.

But a compiler does more than just translate: it can also detect certain errors in your source code (yes, we all make them!). In the end, the compiled program must produce exactly the same results as the original source code.

The compiler is also capable of optimizing the code. Now, does WedoLow develop compilers? No—but here’s the idea. The optimizations offered by the compiler aim to find the representation that yields the best performance while remaining equivalent (though not always perfectly so—we’ll get into that later). They also aim to best match the code to the target hardware it will run on (by specifying, for example, the architecture type using flags like -mcpu, -march, –target, or -mtune). Simple compiler optimizations include things like algebraic simplification or constant propagation.

You can also force the compiler to apply optimizations focusing on a particular axis, such as execution time or memory usage. Energy consumption, however, is not directly targeted by compilation options, as explained in the study below [1] (more on this in the next article 😉).

Ultimately, the compiler’s job is to balance compilation time, binary/program size, and execution speed. These choices inevitably affect how easy it is to debug the compiled code (you can’t win them all).

Compiler: A User’s Guide

So, are all compilers created equal? How should you choose? Unfortunately, the answer isn’t that simple. One study [2], for example, compared two well-known compilers for the x86-64 target: gcc and icc. It measured their performance on a set of C++ applications stressing either the CPU, the I/O system, or both. A Fast Fourier Transform application was also benchmarked. The aspects tested included:

  • function pointers,
  • invariant computations within loops (some compilers still struggle to detect this and move it outside the loop),
  • iterators,
  • constant propagation,
  • loop unrolling,
  • structures/classes.

The execution time results across the benchmarks didn’t allow for a clear winner (that would’ve been too easy). In reality, one compiler tends to outperform the other in specific cases (e.g., gcc for constant propagation).

One key lever for improving code performance through the compiler is the optimization level selected. In other words, should you compile with -O0, -O2, -O3, or even -Os? Here’s the catch: these levels don’t mean the same thing across compilers. For example, certain optimizations allowed at -O2 in icc may not even be permitted at -O3 in gcc!

Beyond optimization levels, another finer-grained lever is adding compiler flags. These customize the optimization scheme the compiler will explore. Examples include inlining functions (watch out for the size vs. speed trade-off), unrolling loops, or allowing certain floating-point optimizations (careful here—possible precision loss). It’s therefore essential to determine exactly which flags are valuable (and acceptable) for a given use case. To spice things up, the combination and ordering of flags can also impact program performance. State-of-the-art machine learning techniques have been proposed to automatically suggest the best flag combinations and ordering. At WedoLow, we’re not targeting these approaches for now—but it’s worth noting that tweaking such “minor” parameters can significantly impact performance.

Naturally, the flags you use impact not only the application’s performance but also compilation time. This was also studied, though we won’t go into detail here.

In Summary

Beyond the initial best practices outlined in our article on software eco-design, there are still other factors to consider when looking at the quality/performance or efficiency trade-off.

Though this gets into the deeper layers of computer science, it’s worth examining the performance of different compilers and their associated flags/options when you have the ability to choose them. There’s no universal rule, but some practice can help you find your winning combination!

One quick way to test different compilers, flags, and options is through the tools and expertise provided by WedoLow.

📩 Contact our team of experts to discuss: sales@wedolow.com

[1] Yuki, Tomofumi, and Sanjay Rajopadhye. Folklore Confirmed: Compiling for Speed Compiling for Energy. International Workshop on Languages and Compilers for Parallel Computing. Cham: Springer International Publishing, 2013.
[2] Botezatu, Mirela. A study on compiler flags and performance events. European Organization for Nuclear Research (2012).

Do you also want me to simplify this translation for a blog-style article (more punchy, less academic), or keep it as-is for a technical white paper?

Ready to optimize your embedded code?

Get started with WedoLow and see how we can transform your software performance