Skip to content

About

A 6-day curriculum for building fundamental skills for performance engineering with C++ systems.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

C++ for High-Performance Engineering: 7-Day Foundational Project Series

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.git

For instructors/reference: You're in the right place! This branch contains implementations.

Results: FINDINGS.md summarizes what each day's measurements showed.

Branch Structure

This repository uses a two-branch approach:

  • template branch: Empty project templates with starter code and TODO comments for learners
  • main branch: 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/main

Repository Structure

cpp-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

Project Overview

Day 1: CMake + CI Hello World

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

  • calculator library with add/subtract/multiply/divide (header-only or .h/.cpp, your call)
  • main.cpp demo, 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-format and 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):

Why it matters: Every real C++ project starts here. Without a reliable build and CI, you can't collaborate on or ship code.


Day 2: Header-Only Math Library

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, constexpr wherever possible
  • Tests, including static_asserts that prove operations evaluate at compile time
  • An INTERFACE CMake target with install/export rules, and a small consumer project that uses it through find_package(vec3)
  • A naive std::chrono timing 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):

Additional resources:

Tools for quick performance checks:

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.


Day 3: Containerized Benchmark Runner

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 chrono loop 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 to results/
  • A short ANALYSIS.md comparing 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):

Additional resources:

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.


Day 4: Data Structure Performance Explorer

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 without reserve), sorted std::vector + binary search, and unsorted std::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++23 std::flat_map
  • Back std::map with a std::pmr arena 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):

Additional resources:

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.


Day 5: Real-Time Data Processor

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,quantity that 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):

Additional resources:

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.


Day 6: Concurrent Task Processor

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 clean close() shutdown
  • A ThreadPool whose submit() returns a std::future (results and exceptions cross threads), plus parallel_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_for grain 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):

Additional resources:

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".


Day 7: TCP Echo Server and Load Generator

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):

Additional resources:

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.


Prerequisites

  • 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

Building a Project

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>/.

Learning Progression

Each day builds on the previous:

  1. Build Systems → You can compile and distribute C++ code professionally
  2. Header Libraries → You understand modern C++ distribution patterns
  3. Containers + Benchmarks → You can measure performance and trust the numbers
  4. Data Structures → You make informed choices about algorithms and containers
  5. Stream Processing → You can process real-time data with predictable latency
  6. Concurrency → You can write thread-safe code and predict when parallelism pays off
  7. Networking → You can build a TCP service and explain where its latency goes

Success Criteria

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

Next Steps

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

Daily Study Schedule

Each day should follow this pattern:

  1. Morning (2-3 hours): Read assigned materials for the day
  2. Afternoon (4-5 hours): Implement the project
  3. 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.

Versioning and Releases

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

Contributing

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.

About

A 6-day curriculum for building fundamental skills for performance engineering with C++ systems.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages