C++ HPC Optimization
Optimize the measured data path, not the code that merely looks low-level.
Preserve a simple reference implementation and make every optimization earn its
complexity with correctness evidence and a representative benchmark.
Non-negotiable rules
- Define the contract first. Record inputs, sizes, distributions, target
hardware, accuracy, determinism, memory, latency, and throughput objectives.
- Pass the complexity gate. Compare the best practical algorithmic families
for the actual size range before tuning constants inside one family.
- Profile before kernel redesign. Identify the hot path with end-to-end evidence.
- Keep an oracle. Retain a scalar or otherwise obviously-correct reference
implementation and compare every optimized path against it.
- Model before tuning. Estimate useful operations, bytes transferred,
working set, dependency depth, synchronization, and launch overhead.
- Change one axis at a time. Measure after each transformation and revert
changes that do not win on the target workload.
- Never encode a benchmark accident as a law. Cache sizes, SIMD width,
thread count, tile size, padding, and crossover thresholds are target- and
workload-dependent.
- Keep a portable fallback. ISA-specific kernels require feature dispatch,
tail handling, and equivalence tests.
Workflow
1. Frame the performance contract
Write down:
- the semantic result and accepted numerical error;
- representative and adversarial input shapes and distributions;
- steady-state throughput, single-item latency, tail latency, or deadline;
- memory-footprint and allocation constraints;
- target CPUs, accelerators, compilers, build flags, and deployment topology;
- whether batching, reordering, nondeterministic reductions, or preprocessing
are permitted.
Do not optimize an unspecified target. Throughput and latency often demand
opposite choices: batching and deep queues improve throughput but add waiting.
2. Pass the algorithmic time/space complexity gate
Before applying cache, SIMD, threading, or instruction-level techniques:
- identify lower bounds and the best mature practical algorithm families for
the declared sizes, distributions, sparsity, dimensionality, accuracy,
update/query ratio, and number of timesteps or solves;
- compare total time, peak/live space, preprocessing, communication, numerical
behavior, worst/expected cases, and implementation maturity;
- benchmark crossover regions and retain a size/shape-dependent portfolio when
different algorithms win in different regimes.
An asymptotically better method may lose at small n; an asymptotically worse
regular kernel may exploit the target exceptionally well. Conversely, no amount
of constant-factor kernel tuning rescues an avoidable complexity class at scale.
Choose the industrially useful family for the declared range first, then use the
rest of this skill to reduce its realized cost. Preserve the declared semantic or
numerical contract; optimization need not be bit-exact when the contract permits
an approximate, reordered, or mixed-precision algorithm.
3. Establish correctness and measurement baselines
- Build a readable reference path before intrinsics, lossy precision, relaxed
math, or concurrency.
- Create correctness tests covering empty, tiny, tail, misaligned, extreme,
NaN/Inf, sparse/dense, and aliasing cases that the contract permits.
- Benchmark the actual hot operation with realistic data. Warm up, prevent
dead-code elimination, report distribution rather than one lucky sample, and
retain end-to-end measurements beside microbenchmarks.
- Inspect generated code and compiler optimization remarks before assuming the
compiler failed.
Read references/profiling-and-cost-model.md before profiling, benchmarking,
or declaring a bottleneck.
4. Classify the limiting resource
Calculate at least an approximate per-item model:
- useful operations and transcendental/divide cost;
- compulsory input, output, metadata, and intermediate bytes;
- working-set size at each reuse distance;
- independent operations versus loop-carried dependency chains;
- tasks, locks, atomics, barriers, launches, copies, and queue transitions;
- scaling versus threads, vector width, batch size, and problem size.
Classify the current regime as memory-bandwidth, cache-capacity/latency,
compute/issue, dependency-latency, synchronization, task-granularity, launch, or
transfer bound. A kernel can move between regimes after each optimization.
5. Evaluate change scope and ROI
Record the currently authorized edit boundary, but do not mistake it for the
root-cause boundary. When evidence places the limiting work upstream or in data
representation, ownership, or construction, compare:
- the best optimization inside the current boundary;
- the smallest upstream, layout, index, or API change that removes the cause;
- the practical blank-slate design, to expose the ceiling of both candidates.
For each candidate, estimate end-to-end speedup, complexity and crossover,
memory, implementation and migration work, write amplification, invariant and
test surface, and ongoing maintenance. If wider scope has credible net value,
present the evidence and obtain user approval before expanding it. Keep the
experiment reversible, measure the representative full workflow, and retain the
wider change only when its observed performance and complexity benefits repay
its lifecycle cost; otherwise revert it.
6. Apply transformations in economic order
Prefer the first measured transformation that attacks the current limit:
- Remove unnecessary work, copies, allocation, conversion, and materialization.
- Reduce footprint and improve data layout or traversal locality.
- Fuse passes or tile/block computation to reuse data before eviction.
- Expose compiler optimization by removing false alias/dependency barriers.
- Vectorize, using intrinsics only when generated code proves they are needed.
- Parallelize with enough work per task and no shared hot write locations.
- Batch, pipeline, or overlap copies and computation when latency can be hidden.
- Offload only when transfer, launch, and synchronization costs fit the model.
Load the relevant references before choosing:
references/data-layout-and-memory.md — DOD, AoS/SoA/AoSoA, packing,
sparse data, locality, tiling, fusion, and Morton order.
references/allocation-and-memory-resources.md — measured allocation
bottlenecks, preallocation, PMR, arenas, pools, TLS scratch, general-purpose
allocators, NUMA, and allocator benchmarks.
references/ragged-topology.md — flattened variable-length arrays, offset
encodings, sidecar topology, packed sparse sets, polygon-corner topology,
aligned attributes, and scan-built output.
references/numerics-and-quantization.md — precision, FP16/BF16, shared
exponent, quantization, accumulation, and numerical validation.
references/simd-and-compiler.md — auto-vectorization, dependencies,
intrinsics, SIMD tricks, tails, and ISA dispatch.
references/parallelism-and-pipelines.md — thread degree, TLS reduction,
false sharing, work stealing, queues, async launch, and pipelines.
references/accelerator-throughput.md — CPU/accelerator transfer, CUDA-style
streams, occupancy, synchronization, and launch amortization.
references/hotpath-polymorphism.md — hot/cold boundaries, abstraction cost,
and data-oriented polymorphism.
7. Validate and integrate
- Compare every output with the reference under the declared error metric.
- Run sanitizers and boundary tests before trusting benchmark results.
- Measure the same workload, hardware state, compiler, and flags before/after.
- Check performance across small, crossover, and large sizes; optimized kernels
often lose below a threshold.
- Preserve the readable fallback and dispatch outside the inner loop.
- Record assumptions, selected thresholds, measured results, and rejected
alternatives near the benchmark or design documentation.
- Keep cold-path architecture maintainable. Do not spread kernel-specific data
layout or ISA details across the rest of the system.
Relationship to C++ OOP design
Apply $cpp-oop-style to ownership, orchestration, I/O, error handling, and
module boundaries. Inside a measured hot kernel, prefer flat data, value views,
batch operations, static dispatch, and explicit SIMD when evidence requires it.
Use its debug-instrumentation guidance for correctness and runtime diagnosis;
this skill owns profiling, tracing, counters, and benchmarks used to locate or
quantify a performance bottleneck.
The boundary should normally look like:
- abstract behavior and resource ownership on the cold/control side;
- data-only request/config/result types at the seam;
- one dense concrete pool per hot subtype, with homogeneous spans or tiles
entering the hot/data side;
- dispatch once per batch, never once per element;
- a reference kernel and one or more selected optimized implementations.
Read references/hotpath-polymorphism.md before removing abstractions or adding
type tags. Virtual dispatch is rarely the largest cost by itself; the lost
inlining, scattered objects, unpredictable branches, and pointer-chasing around
it are often the actual problem.
Source material and provenance
Read references/parallel101-case-studies.md when looking for concrete lesson
progressions or deeper examples from archibate's parallel101/course and
parallel101/simdtutor repositories. Search the bundled offline corpus under
references/parallel101/ before depending on a maintainer's checkout or the
network. Use references/parallel101/provenance.tsv to recover the repository,
author, source URL, commit, original path, teaching classification, license, and
SHA-256 for every excerpt.
Treat the corpus as educational experiments, not production code. The case-study
index marks outdated, unsafe, incomplete, or broken examples so their ideas can
be learned without copying their defects. The skill and bundled corpus are
licensed under CC BY-NC-SA 4.0; retain attribution and compatible terms when
redistributing adaptations.
1---2name: cpp-hpc-optimization3description: Evidence-driven C++ high-performance computing design, profiling, and optimization across algorithmic time/space complexity, data layout, cache and memory behavior, numerics, SIMD, multicore scheduling, and CPU/accelerator pipelines. Use before designing or changing computation-intensive kernels, high-throughput data structures, performance-critical loops, SIMD/OpenMP/TBB/CUDA code, mixed-precision or sparse representations, or when investigating throughput, latency, scaling, cache, bandwidth, compiler-vectorization, or profiling problems.4---56# C++ HPC Optimization78Optimize the measured data path, not the code that merely looks low-level.9Preserve a simple reference implementation and make every optimization earn its10complexity with correctness evidence and a representative benchmark.1112## Non-negotiable rules13141. **Define the contract first.** Record inputs, sizes, distributions, target15 hardware, accuracy, determinism, memory, latency, and throughput objectives.162. **Pass the complexity gate.** Compare the best practical algorithmic families17 for the actual size range before tuning constants inside one family.183. **Profile before kernel redesign.** Identify the hot path with end-to-end evidence.194. **Keep an oracle.** Retain a scalar or otherwise obviously-correct reference20 implementation and compare every optimized path against it.215. **Model before tuning.** Estimate useful operations, bytes transferred,22 working set, dependency depth, synchronization, and launch overhead.236. **Change one axis at a time.** Measure after each transformation and revert24 changes that do not win on the target workload.257. **Never encode a benchmark accident as a law.** Cache sizes, SIMD width,26 thread count, tile size, padding, and crossover thresholds are target- and27 workload-dependent.288. **Keep a portable fallback.** ISA-specific kernels require feature dispatch,29 tail handling, and equivalence tests.3031## Workflow3233### 1. Frame the performance contract3435Write down:3637- the semantic result and accepted numerical error;38- representative and adversarial input shapes and distributions;39- steady-state throughput, single-item latency, tail latency, or deadline;40- memory-footprint and allocation constraints;41- target CPUs, accelerators, compilers, build flags, and deployment topology;42- whether batching, reordering, nondeterministic reductions, or preprocessing43 are permitted.4445Do not optimize an unspecified target. Throughput and latency often demand46opposite choices: batching and deep queues improve throughput but add waiting.4748### 2. Pass the algorithmic time/space complexity gate4950Before applying cache, SIMD, threading, or instruction-level techniques:5152- identify lower bounds and the best mature practical algorithm families for53 the declared sizes, distributions, sparsity, dimensionality, accuracy,54 update/query ratio, and number of timesteps or solves;55- compare total time, peak/live space, preprocessing, communication, numerical56 behavior, worst/expected cases, and implementation maturity;57- benchmark crossover regions and retain a size/shape-dependent portfolio when58 different algorithms win in different regimes.5960An asymptotically better method may lose at small `n`; an asymptotically worse61regular kernel may exploit the target exceptionally well. Conversely, no amount62of constant-factor kernel tuning rescues an avoidable complexity class at scale.63Choose the industrially useful family for the declared range first, then use the64rest of this skill to reduce its realized cost. Preserve the declared semantic or65numerical contract; optimization need not be bit-exact when the contract permits66an approximate, reordered, or mixed-precision algorithm.6768### 3. Establish correctness and measurement baselines6970- Build a readable reference path before intrinsics, lossy precision, relaxed71 math, or concurrency.72- Create correctness tests covering empty, tiny, tail, misaligned, extreme,73 NaN/Inf, sparse/dense, and aliasing cases that the contract permits.74- Benchmark the actual hot operation with realistic data. Warm up, prevent75 dead-code elimination, report distribution rather than one lucky sample, and76 retain end-to-end measurements beside microbenchmarks.77- Inspect generated code and compiler optimization remarks before assuming the78 compiler failed.7980Read `references/profiling-and-cost-model.md` before profiling, benchmarking,81or declaring a bottleneck.8283### 4. Classify the limiting resource8485Calculate at least an approximate per-item model:8687- useful operations and transcendental/divide cost;88- compulsory input, output, metadata, and intermediate bytes;89- working-set size at each reuse distance;90- independent operations versus loop-carried dependency chains;91- tasks, locks, atomics, barriers, launches, copies, and queue transitions;92- scaling versus threads, vector width, batch size, and problem size.9394Classify the current regime as memory-bandwidth, cache-capacity/latency,95compute/issue, dependency-latency, synchronization, task-granularity, launch, or96transfer bound. A kernel can move between regimes after each optimization.9798### 5. Evaluate change scope and ROI99100Record the currently authorized edit boundary, but do not mistake it for the101root-cause boundary. When evidence places the limiting work upstream or in data102representation, ownership, or construction, compare:103104- the best optimization inside the current boundary;105- the smallest upstream, layout, index, or API change that removes the cause;106- the practical blank-slate design, to expose the ceiling of both candidates.107108For each candidate, estimate end-to-end speedup, complexity and crossover,109memory, implementation and migration work, write amplification, invariant and110test surface, and ongoing maintenance. If wider scope has credible net value,111present the evidence and obtain user approval before expanding it. Keep the112experiment reversible, measure the representative full workflow, and retain the113wider change only when its observed performance and complexity benefits repay114its lifecycle cost; otherwise revert it.115116### 6. Apply transformations in economic order117118Prefer the first measured transformation that attacks the current limit:1191201. Remove unnecessary work, copies, allocation, conversion, and materialization.1212. Reduce footprint and improve data layout or traversal locality.1223. Fuse passes or tile/block computation to reuse data before eviction.1234. Expose compiler optimization by removing false alias/dependency barriers.1245. Vectorize, using intrinsics only when generated code proves they are needed.1256. Parallelize with enough work per task and no shared hot write locations.1267. Batch, pipeline, or overlap copies and computation when latency can be hidden.1278. Offload only when transfer, launch, and synchronization costs fit the model.128129Load the relevant references before choosing:130131- `references/data-layout-and-memory.md` — DOD, AoS/SoA/AoSoA, packing,132 sparse data, locality, tiling, fusion, and Morton order.133- `references/allocation-and-memory-resources.md` — measured allocation134 bottlenecks, preallocation, PMR, arenas, pools, TLS scratch, general-purpose135 allocators, NUMA, and allocator benchmarks.136- `references/ragged-topology.md` — flattened variable-length arrays, offset137 encodings, sidecar topology, packed sparse sets, polygon-corner topology,138 aligned attributes, and scan-built output.139- `references/numerics-and-quantization.md` — precision, FP16/BF16, shared140 exponent, quantization, accumulation, and numerical validation.141- `references/simd-and-compiler.md` — auto-vectorization, dependencies,142 intrinsics, SIMD tricks, tails, and ISA dispatch.143- `references/parallelism-and-pipelines.md` — thread degree, TLS reduction,144 false sharing, work stealing, queues, async launch, and pipelines.145- `references/accelerator-throughput.md` — CPU/accelerator transfer, CUDA-style146 streams, occupancy, synchronization, and launch amortization.147- `references/hotpath-polymorphism.md` — hot/cold boundaries, abstraction cost,148 and data-oriented polymorphism.149150### 7. Validate and integrate151152- Compare every output with the reference under the declared error metric.153- Run sanitizers and boundary tests before trusting benchmark results.154- Measure the same workload, hardware state, compiler, and flags before/after.155- Check performance across small, crossover, and large sizes; optimized kernels156 often lose below a threshold.157- Preserve the readable fallback and dispatch outside the inner loop.158- Record assumptions, selected thresholds, measured results, and rejected159 alternatives near the benchmark or design documentation.160- Keep cold-path architecture maintainable. Do not spread kernel-specific data161 layout or ISA details across the rest of the system.162163## Relationship to C++ OOP design164165Apply `$cpp-oop-style` to ownership, orchestration, I/O, error handling, and166module boundaries. Inside a measured hot kernel, prefer flat data, value views,167batch operations, static dispatch, and explicit SIMD when evidence requires it.168Use its debug-instrumentation guidance for correctness and runtime diagnosis;169this skill owns profiling, tracing, counters, and benchmarks used to locate or170quantify a performance bottleneck.171172The boundary should normally look like:173174- abstract behavior and resource ownership on the cold/control side;175- data-only request/config/result types at the seam;176- one dense concrete pool per hot subtype, with homogeneous spans or tiles177 entering the hot/data side;178- dispatch once per batch, never once per element;179- a reference kernel and one or more selected optimized implementations.180181Read `references/hotpath-polymorphism.md` before removing abstractions or adding182type tags. Virtual dispatch is rarely the largest cost by itself; the lost183inlining, scattered objects, unpredictable branches, and pointer-chasing around184it are often the actual problem.185186## Source material and provenance187188Read `references/parallel101-case-studies.md` when looking for concrete lesson189progressions or deeper examples from archibate's `parallel101/course` and190`parallel101/simdtutor` repositories. Search the bundled offline corpus under191`references/parallel101/` before depending on a maintainer's checkout or the192network. Use `references/parallel101/provenance.tsv` to recover the repository,193author, source URL, commit, original path, teaching classification, license, and194SHA-256 for every excerpt.195196Treat the corpus as educational experiments, not production code. The case-study197index marks outdated, unsafe, incomplete, or broken examples so their ideas can198be learned without copying their defects. The skill and bundled corpus are199licensed under CC BY-NC-SA 4.0; retain attribution and compatible terms when200redistributing adaptations.