A progressive series of one-day C++ projects designed to build the foundational skills needed for high-performance systems development. Each project introduces essential concepts while building toward the competence required for high-performance engineering. Some of these projects may take longer than a day, and that's fine!
Complete Solutions: This is the main branch containing project implementations.
For learners: Clone the template branch to get started:
git clone -b template https://github.com/t-flora/cpp-perf-foundations.gitFor instructors/reference: You're in the right place! This branch contains implementations.
Results: FINDINGS.md summarizes what each day's measurements showed.
This repository uses a two-branch approach:
templatebranch: Empty project templates with starter code and TODO comments for learnersmainbranch: Complete implementations and solutions for reference
Workflow for learners:
# Clone template branch to start learning
git clone -b template https://github.com/t-flora/cpp-perf-foundations.git
cd cpp-perf-foundations
# Work on your solutions...
# Optional: Compare with complete solutions
git remote add upstream https://github.com/t-flora/cpp-perf-foundations.git
git fetch upstream main
git checkout -b solutions upstream/maincpp-foundations/
├── README.md # This file
├── FINDINGS.md # What the measurements showed, across all days
├── .actrc # act config: run the GitHub Actions workflow locally
├── scripts/
│ ├── test_all.sh # Build and test every day
│ ├── simulate-ci.sh # Day 1 CI steps on your machine
│ └── simulate-ci-docker.sh # Day 1 CI steps in an Ubuntu container
├── .github/
│ └── workflows/
│ └── day1-ci.yml # GitHub Actions CI for Day 1
├── day1-cmake-ci/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── build_and_test.sh
│ ├── src/
│ │ ├── CMakeLists.txt
│ │ ├── calculator.hpp
│ │ └── main.cpp
│ └── tests/
│ ├── CMakeLists.txt
│ └── test_calculator.cpp
├── day2-header-library/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── cmake/
│ │ └── vec3Config.cmake.in
│ ├── include/
│ │ └── vec3/
│ │ └── vec3.hpp
│ ├── examples/
│ │ └── consumer/ # Uses the installed library via find_package
│ ├── benchmarks/
│ │ ├── simple_benchmark.cpp
│ │ └── bench_vec3.cpp
│ └── tests/
│ ├── test_vec3_basics.cpp
│ └── test_vec3_constexpr.cpp
├── day3-docker-benchmarks/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── Dockerfile
│ ├── run_benchmarks.sh
│ ├── include/
│ │ └── sorting/
│ │ ├── sorting.hpp
│ │ └── inputs.hpp
│ ├── benchmarks/
│ │ └── bench_sorting.cpp
│ ├── tests/
│ │ └── test_sorting.cpp
│ └── results/
│ └── ANALYSIS.md
├── day4-container-performance/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── containers/
│ │ └── workloads.hpp
│ ├── benchmarks/
│ │ └── bench_containers.cpp
│ ├── tests/
│ │ └── test_workloads.cpp
│ ├── scripts/
│ │ └── plot_results.py
│ └── results/
│ └── DECISION_GUIDE.md
├── day5-realtime-processor/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── stream/ # trade parsing, ring buffer, window, histogram, pipelines
│ ├── src/
│ │ └── processor.cpp
│ ├── scripts/
│ │ └── generate_trades.py
│ ├── data/
│ │ └── sample_trades.csv
│ ├── tests/
│ ├── benchmarks/
│ │ └── bench_components.cpp
│ └── results/
│ └── ANALYSIS.md
├── day6-concurrent-calculator/
│ ├── README.md
│ ├── CMakeLists.txt
│ ├── include/
│ │ └── concurrency/ # bounded_queue.hpp, thread_pool.hpp, workloads.hpp
│ ├── tests/
│ ├── examples/
│ │ └── race_demo.cpp
│ ├── benchmarks/
│ │ └── bench_scaling.cpp
│ ├── scripts/
│ │ └── plot_scaling.py
│ └── results/
│ └── ANALYSIS.md
└── day7-tcp-echo/
├── README.md
├── CMakeLists.txt
├── Dockerfile
├── include/
│ └── net/ # socket.hpp, framing.hpp, echo_server.hpp
├── src/ # echo_server, echo_client, transport_probe
├── tests/
├── scripts/ # run_experiments.py, plot_results.py
└── results/
└── ANALYSIS.md
Goal: Master the build system every C++ project uses
What You'll Build: A small calculator library, a demo executable, and a CI pipeline that builds and tests both
calculatorlibrary with add/subtract/multiply/divide (header-only or.h/.cpp, your call)main.cppdemo, plus a test executable registered with CTest- Target-based CMake: one library target that the executables link against
- GitHub Actions workflow that builds and tests on your platform
If time permits:
- Extend the CI matrix to Linux, macOS, and Windows
- Run the workflow locally with act
- Add a
.clang-formatand check formatting in CI
Skills Gained:
- CMake targets, linking, and usage requirements
- CI with GitHub Actions
- Project layout and consistent code style
Essential Reading (2-3 hours before coding):
- CMake Tutorial - Official tutorial, steps 1-4
- Modern CMake: The Basics - Target-based CMake
- GitHub Actions CMake Workflow - Starter template
- CMake Template Project - Complete example with CI
Why it matters: Every real C++ project starts here. Without a reliable build and CI, you can't collaborate on or ship code.
Goal: Understand how modern C++ libraries are distributed
What You'll Build: A templated 3D vector type, Vec3<T>, in a single header
- Arithmetic operators,
dot,cross,length,normalized,constexprwherever possible - Tests, including
static_asserts that prove operations evaluate at compile time - An
INTERFACECMake target with install/export rules, and a small consumer project that uses it throughfind_package(vec3) - A naive
std::chronotiming loop over your operations. Note what makes it unreliable; Day 3 replaces it
If time permits:
- Expression templates for chains like
a + b + c; compare the assembly on Compiler Explorer - Switch the tests to Google Test
Skills Gained:
- Templates and
constexpr - Header-only library design and CMake packaging
- API design principles
Essential Reading (2-3 hours before coding):
- Effective Modern C++ - Item 30 - Perfect forwarding and where it breaks
- Expression Templates Explained (ModernesCpp) - Removing temporaries from math expressions
- C++ Reference: constexpr specifier - Compile-time computation
- Google Highway SIMD Library - Cross-platform vectorization basics
- API Design Principles - Designing clean interfaces
Additional resources:
- Modern C++ Header-Only Libraries - Examples to learn from
- Google Test Primer - Testing fundamentals
- Importing and Exporting Guide - Making
find_packagework for your library
Tools for quick performance checks:
- Compiler Explorer - See the assembly your code produces
- Quick Bench - Online benchmarking for snippets
- C++ Insights - See what templates instantiate to
Why it matters: Many high-performance libraries (Eigen, GLM, much of Boost) are header-only: the compiler sees every definition, so it can inline and optimize across calls. You'll use libraries like this constantly, and eventually write them.
Goal: Measure performance correctly, and make the measurements reproducible with Docker
What You'll Build: A Google Benchmark suite comparing sorting algorithms, packaged in a multi-stage Docker image
- Warm-up: port your Day 2
chronoloop to Google Benchmark and explain why the numbers differ - Your own quicksort and LSD radix sort, tested for correctness against
std::sort - Benchmarks across sizes (16 to 1M elements) and input distributions (random, sorted, reversed, few unique), with repetitions and JSON output
- Multi-stage Dockerfile: toolchain in the build stage, only the binary in the runtime stage
run_benchmarks.sh, which builds the image, runs it, and writes timestamped results toresults/- A short
ANALYSIS.mdcomparing the algorithms, and native vs. containerized runs
If time permits:
- Plot ns/element vs. size for each distribution
- A CI job that builds the image and smoke-runs the benchmarks (never gate CI on timings)
Skills Gained:
- Benchmark design: defeating the optimizer, controlling timers, repetitions
- Reading results statistically instead of anecdotally
- Multi-stage Docker builds for C++
- Fetching dependencies with CMake
FetchContent
Essential Reading (2-3 hours before coding):
- Google Benchmark User Guide - Passing arguments, asymptotic complexity, controlling timers, preventing optimization, repetitions
- Reducing Variance - Why your numbers jump around
- Tuning C++: Benchmarks, and CPUs, and Compilers! Oh My! - Chandler Carruth, CppCon 2015; live benchmarking pitfalls
- Docker Multi-Stage Builds - Separating build and runtime images
Additional resources:
- Google Benchmark Tools -
compare.pyfor statistically comparing two runs - Radix Sort Revisited - Practical LSD radix sort for 32-bit keys
- CMake FetchContent - Pulling dependencies at configure time
- Docker C++ Guide - Containerizing a C++ project end to end
Why it matters: Optimization decisions are only as good as the measurements behind them. A number you can't reproduce on another machine, in CI, or next month is an anecdote. Benchmark discipline plus a pinned environment turns it into evidence.
Goal: Know which container to reach for, backed by your own measurements
What You'll Build: A benchmark suite comparing key-value containers and sequences, plus plots and a decision guide
std::map,std::unordered_map(with and withoutreserve), sortedstd::vector+ binary search, and unsortedstd::vector+ linear search, behind one interface and tested to give identical answers- Lookup (random vs. sequential keys), build, and iterate benchmarks from 16 to 1M elements
- The classic vector vs. list sorted-insertion experiment
- A Python script that turns the JSON results into a CSV and log-log plots
- A decision guide where every recommendation cites a measurement
If time permits:
- Add an open-addressing hash map (
absl::flat_hash_map,boost::unordered_flat_map) or C++23std::flat_map - Back
std::mapwith astd::pmrarena and measure how much of its cost is allocation
Skills Gained:
- How caches turn the same Big-O into very different running times
- Container internals: nodes vs. contiguous storage, hashing, rehashing
- Benchmarking across sizes and access patterns
- Data analysis and visualization with Python
Essential Reading (2-3 hours before coding):
- Efficiency with Algorithms, Performance with Data Structures - Chandler Carruth, CppCon 2014; why contiguous memory wins
- Gallery of Processor Cache Effects - Seven short experiments to reproduce in your head
- Containers Library (cppreference) - Complexity guarantees for every container
- What Is Cache-Friendly Code? - Memory layout and performance
Additional resources:
- Designing a Fast, Efficient, Cache-friendly Hash Table - Matt Kulukundis, CppCon 2017; what
std::unordered_mapleaves on the table - Abseil Containers Guide - Flat vs. node hash maps in practice
- What Every Programmer Should Know About Memory - Ulrich Drepper; sections 3 and 6
- When to Use Which Container - Practical decision guide
Why it matters: Big-O hides the constant, and on modern hardware the constant is mostly memory: one cache miss costs as much as hundreds of arithmetic operations. The wrong container can cost an order of magnitude, and only measurement shows where.
Goal: Process a stream record by record, with bounded memory and predictable latency
What You'll Build: A trade-stream processor that keeps per-symbol sliding-window statistics, in two versions with identical output
- A CSV parser for
timestamp_ns,symbol,price,quantitythat rejects malformed and late records, counting each reason - A fixed-capacity ring buffer and a time-based sliding window with O(1) VWAP and moving average, using integer prices so the running sums are exact
- Event-time snapshots of every active symbol at a configurable interval
- A naive pipeline (
getline,stod,std::map,std::deque,std::endl) and a fast one (string_view,from_chars, interned ids, ring buffer, batched output), tested to produce byte-identical output - Per-record latency percentiles (p50 to p99.99) from an allocation-free histogram, plus component benchmarks that explain the difference
If time permits:
- Sliding-window high/low in O(1) amortized, with a monotonic queue
- Accept late records within an allowed lateness instead of rejecting them
Skills Gained:
- Stream processing: event time, windows, late data
- Ring buffers and allocation-free hot paths
- Fast, strict parsing with
std::from_chars - Measuring latency distributions, and the limits of your clock
Essential Reading (2-3 hours before coding):
- How NOT to Measure Latency - Gil Tene; why averages lie and percentiles matter
- Streaming 101 - Tyler Akidau; event time vs. processing time, windows
- I've Been Writing Ring Buffers Wrong All These Years - Power-of-two capacity and index masking
- std::from_chars (cppreference) - Locale-free, allocation-free number parsing
Additional resources:
- When a Microsecond Is an Eternity - Carl Cook, CppCon 2017; low-latency C++ in trading systems
- Volume-Weighted Average Price - The statistic you're computing
- HdrHistogram - The design your latency histogram simplifies
- Minimum Queue - For the sliding-window high/low stretch goal
Why it matters: Real-time systems are judged by their worst moments, not their averages. A stream processor with a great mean and a 100 µs hiccup every thousand records will miss exactly the events that matter, and only percentile measurements show it.
Goal: Write thread-safe building blocks, and measure what parallelism actually buys
What You'll Build: A thread pool on a bounded blocking queue, tested under ThreadSanitizer, then used for scaling and contention experiments
- A multi-producer, multi-consumer
BoundedQueue(std::mutex+std::condition_variable) with backpressure and a cleanclose()shutdown - A
ThreadPoolwhosesubmit()returns astd::future(results and exceptions cross threads), plusparallel_for - Thread-safety tests: every item delivered exactly once under many producers and consumers, blocking and wakeup behavior, shutdown that drains the queue
- Scaling benchmarks: a compute-bound workload (prime counting, static vs. dynamic partitioning) vs. a memory-bound one (summing 256 MB)
- Contention benchmarks: a mutex, a shared atomic, and per-thread counters with and without cache-line padding
If time permits:
- Sweep the
parallel_forgrain size and find where queue overhead starts to dominate - Move Day 5's snapshot output to a writer thread through your queue, and re-measure p99.99
Skills Gained:
std::thread,std::mutex,std::condition_variable,std::future- Thread pool design, shutdown, and exception propagation
- Finding data races with ThreadSanitizer
- Reading scaling curves: load balance, memory bandwidth, false sharing, Amdahl's law
Essential Reading (2-3 hours before coding):
- (Paid textbook) C++ Concurrency in Action - Chapters 2-4 - Managing threads, sharing data, synchronizing with condition variables and futures
- Concurrency Support Library (cppreference) - Threads, mutexes, condition variables, futures
- C++ Atomics, From Basic to Advanced - Fedor Pikus, CppCon 2017; what atomics really cost
- Core Guidelines - Concurrency - Rules worth following from day one
Additional resources:
- Better Code: Concurrency - Sean Parent; tasks over raw threads and locks
- ThreadSanitizer - How to run it and read its reports
- std::hardware_destructive_interference_size - The standard's answer to "how much padding?" (check it against your hardware)
- Amdahl's Law - Why speedup curves flatten
- atomic Weapons - Herb Sutter; the memory model in depth
Why it matters: More cores only help when the work divides evenly, the data doesn't fight over cache lines, and the bottleneck is compute rather than memory. Thread-safe building blocks and the measurements to check those three things are what separate "it's multithreaded" from "it's faster".
Goal: Learn socket programming, and find out what a network round trip costs before the network is even involved
What You'll Build: A length-prefixed echo server and a client that measures it
- A small RAII socket layer: listen, accept, connect,
send_all(partial writes),recv_exact(partial reads) - Length-prefixed framing, with a decoder that reassembles messages from however TCP splits them, and refuses absurd lengths
- An echo server with a thread per connection, and a clean
stop() - A load generator: N connections, each with D requests in flight, reporting throughput and p50/p99/p99.9 round-trip time
- Experiments: message size, Nagle's algorithm × split writes, pipelining depth, and connection count, on two OSes (native and in Docker)
If time permits:
- A single-threaded
poll()server, and a comparison with thread per connection as connections grow - A probe comparing pipe, Unix-socket, and TCP round trips, blocking vs. spinning, to find where the time goes
- Run the client on a second machine and see what a real network adds
Skills Gained:
- POSIX sockets:
socket,bind,listen,accept,connect,send,recv,poll, socket options - Message framing over a byte stream, and defensive parsing of untrusted input
- Nagle's algorithm, delayed ACKs, and
TCP_NODELAY - Latency vs. throughput: pipelining, Little's law, coordinated omission
- Blocking vs. event-driven server design, and backpressure
Essential Reading (2-3 hours before coding):
- Beej's Guide to Network Programming - Chapters 1-6 (sockets through a working client and server), plus 7.2 (
poll()), 7.4 (partialsend()s), and 7.6 (framing your messages) - Nagle's algorithm - Including its interaction with delayed ACKs
- High Performance Browser Networking, Chapter 2 - TCP's building blocks: handshakes, flow control, slow start, head-of-line blocking
Additional resources:
- The C10K Problem - Why event loops exist
- How NOT to Measure Latency - Gil Tene on coordinated omission and percentiles
- socket(7) and tcp(7) - Socket options on Linux
- UDP vs. TCP (HPBN, Chapter 3) - What you give up, and gain, without a connection
Why it matters: Distributed systems, trading systems, and microservices all spend their latency budget on round trips. Knowing what a round trip costs on loopback, and which OS settings and write patterns multiply it, is what lets you tell a slow network from a slow program.
- A C++20 compiler (GCC 10+, Clang 12+, or MSVC 2019 16.10+); Day 1 only needs C++17
- CMake 3.16+
- Git
- Docker (Days 3 and 7)
- Python 3.8+ with numpy and scipy (Day 3
compare.py) and matplotlib (Day 4, 6, and 7 plots); Days 5 and 7 otherwise need only the standard library
Each day is a standalone CMake project:
cd day1-cmake-ci
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
(cd build && ctest --verbose)scripts/test_all.sh builds and tests every day (or only the ones you name) in build/<day>/.
Each day builds on the previous:
- Build Systems → You can compile and distribute C++ code professionally
- Header Libraries → You understand modern C++ distribution patterns
- Containers + Benchmarks → You can measure performance and trust the numbers
- Data Structures → You make informed choices about algorithms and containers
- Stream Processing → You can process real-time data with predictable latency
- Concurrency → You can write thread-safe code and predict when parallelism pays off
- Networking → You can build a TCP service and explain where its latency goes
After completing all 7 projects, you should be able to:
- Set up a new C++ project with CMake and CI from scratch
- Design and implement header-only libraries
- Write and interpret performance benchmarks
- Choose appropriate data structures for different use cases
- Process streaming data efficiently
- Write thread-safe concurrent code
- Containerize C++ applications for deployment
With these foundational skills mastered, you'll be ready to tackle more ambitious projects like:
- High-performance order book implementation
- Low-latency data processing systems
- Distributed system components
- Real-time analytics engines
Each day should follow this pattern:
- Morning (2-3 hours): Read assigned materials for the day
- Afternoon (4-5 hours): Implement the project
- Evening (1 hour): Document learnings and prepare for next day
Time Management: Don't spend more than 3 hours on reading per day. The goal is practical competence, not theoretical mastery.
This project follows semantic versioning adapted for educational content:
- Major: Breaking changes to project structure or prerequisites
- Minor: New projects or significant enhancements
- Patch: Bug fixes and documentation updates
This is a learning repository. Each project should be:
- Completable in one focused day (6-8 hours)
- Well-documented with clear learning objectives
- Professionally structured with proper testing
- Incrementally building toward real-world skills
Remember: The goal isn't to build the most sophisticated systems possible, but to develop the foundational competence that makes sophisticated systems achievable in the first place.