C++
algorithm comparison
optimization level
g++ compiler
performance analysis

What is the optimization level g you use while comparing two different algorithms written in C?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

When comparing two algorithms in C or C++, the most important rule is not "pick one magic optimization flag." The important rule is to benchmark both implementations under the same realistic build settings, because compiler optimization can change the constant factors dramatically and can even erase code that is not used correctly in the benchmark.

Use Release-Level Optimizations, Not -O0

If the goal is algorithm performance, -O0 is usually the wrong choice. It is useful for debugging, but it does not represent how production code is typically built.

For most fair comparisons, start with -O2 or -O3 and use the same flags for both programs:

bash
g++ -O2 -std=c++20 benchmark.cpp -o benchmark

-O2 is a common default for honest performance testing because it applies strong optimizations without becoming as aggressive as -Ofast. -O3 can also be useful, especially if the production build uses it, but it may favor one implementation more than another because of inlining, vectorization, or loop transformations.

The key point is consistency: both algorithms must be compiled with the same toolchain and the same flags.

Match the Benchmark to the Intended Use Case

If the code will ship with -O3, benchmark with -O3. If the code will run in a normal release build with -O2, benchmark with -O2. The right flag is the one that matches the environment you care about.

A simple benchmark skeleton in C++ looks like this:

cpp
1#include <algorithm>
2#include <chrono>
3#include <iostream>
4#include <numeric>
5#include <random>
6#include <vector>
7
8void algorithm_a(std::vector<int>& v) {
9    std::sort(v.begin(), v.end());
10}
11
12void algorithm_b(std::vector<int>& v) {
13    std::stable_sort(v.begin(), v.end());
14}
15
16int main() {
17    std::mt19937 rng(123);
18    std::uniform_int_distribution<int> dist(1, 1'000'000);
19    std::vector<int> data(100000);
20
21    for (int& x : data) {
22        x = dist(rng);
23    }
24
25    auto run = [&](auto fn, const char* name) {
26        auto copy = data;
27        auto start = std::chrono::steady_clock::now();
28        fn(copy);
29        auto end = std::chrono::steady_clock::now();
30        std::cout << name << ": "
31                  << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
32                  << " us\n";
33    };
34
35    run(algorithm_a, "algorithm_a");
36    run(algorithm_b, "algorithm_b");
37}

This is not a perfect microbenchmark, but it is good enough to illustrate the bigger issue: build flags affect the result.

Compare More Than One Optimization Level

In serious work, it is often worth testing more than one level:

  • '-O2 for a conservative release comparison'
  • '-O3 for a more aggressive performance comparison'
  • '-Ofast only if relaxed language and floating-point rules are acceptable'

If one algorithm only wins under an extreme flag but loses under ordinary release settings, that is useful information. The benchmark should help you make a deployment decision, not just produce the prettiest number.

Keep Other Variables Fixed

Optimization level is only one part of a valid comparison. Keep these constant too:

  • Compiler version
  • Standard library implementation
  • CPU architecture flags
  • Input sizes and distributions
  • Number of benchmark repetitions

If you compile one algorithm with -march=native and the other without it, or benchmark them with different data distributions, the comparison stops being meaningful.

Common Pitfalls

The biggest mistake is benchmarking with -O0 and then drawing conclusions about real-world speed. That mostly measures debug-build behavior, not algorithm performance.

Another common issue is letting the compiler optimize away work. If the benchmark computes something and never uses the result, the compiler may remove part of the algorithm entirely. Always consume the output in some visible way.

Developers also sometimes compare different code quality rather than different algorithms. If one implementation has unnecessary allocations, poor memory layout, or accidental copies, the benchmark may be measuring implementation mistakes more than algorithmic differences.

Finally, do not assume the fastest result under -Ofast is always the best engineering choice. That flag can change semantics in ways that are unacceptable for some numeric or standards-sensitive code.

Summary

  • Use the same compiler and the same optimization flags for both algorithms.
  • Benchmark release-style builds such as -O2 or -O3, not -O0.
  • Choose the optimization level that matches the way the code will actually be shipped.
  • Test multiple optimization levels if the deployment target is uncertain.
  • Control other variables so the benchmark measures the algorithms, not the environment.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms