Vectorization
Modern CPUs get a large share of their performance from SIMD (Single Instruction, Multiple Data) vector instructions: SSE, AVX, AVX-512, and so on. Writing code that reliably benefits from them is not just a matter of enabling a compiler flag: data needs to be properly aligned in memory, the compiler needs to be told when it can trust that alignment, and a binary compiled for one instruction set will either refuse to run, or silently fail to use the newer instructions, on a CPU that doesn't match. Phoenix2 addresses both sides of this problem with two libraries from the hardware group.
Writing vectorization-friendly code: PhoenixHardware
PhoenixHardware provides the low-level building blocks needed to write code that the compiler can actually vectorize:
- Aligned allocation:
PAlignedAllocatorandphoenix_allocAlignedVectorallocate memory aligned to the natural width required by a given type, so that vector loads and stores can operate at full speed. - Alignment hints:
phoenix_assume_alignedwraps the compiler's__builtin_assume_alignedintrinsic, letting the compiler know a pointer is properly aligned so it can vectorize loops it would otherwise leave untouched. - Cache-friendly transposition:
phoenix_transposeprovides block-based transpose utilities (2D and 3D), reshaping data in a way that keeps memory access patterns friendly to both the cache and the vectorizer. - Hardware characteristics: helpers to query available RAM, number of CPU cores, and machine identity, useful to adapt an algorithm's behavior to the machine it runs on.
- Low-overhead timing: a
rdtsc-based CPU-cycle counter, for timing measurements with minimal overhead. - Profiling:
PProfiler, which tracks per-function (and per-thread) performance over time and can export results as CSV for further analysis.
Together, these building blocks remove the repetitive, easy-to-get-wrong plumbing usually required to write hand-optimized, vectorized C++ code.
Getting vectorized code onto every machine: PhoenixHPCProxy
Compiling a program for a specific instruction set (say, AVX-512) gives the best performance on recent CPUs, but the resulting binary will crash on older ones. Compiling for the lowest common denominator instead runs everywhere, but leaves the performance of newer machines on the table. PhoenixHPCProxy solves this with runtime CPU dispatch.
Given a header describing a set of performance-critical functions (for example a hadamard_product, an element-wise vector multiplication), and a version of that function already compiled for each targeted instruction set (SSE, SSE2, SSSE3, SSE4.1, SSE4.2, AVX, AVX2, AVX512F, AVX512BW), the phoenix_hpc_proxy tool generates a small proxy loader. At runtime, the first time a function is called, the proxy loader detects which instruction sets the current CPU actually supports, and transparently loads the fastest compatible variant. From then on, calling code just calls the function normally; the dispatch happens once, behind the scenes.
flowchart LR
H["Header describing<br/>the functions"] --> Gen["phoenix_hpc_proxy<br/>(code generator)"]
Libs["Pre-built variants<br/>(SSE, AVX2, AVX512, ...)"] --> Gen
Gen --> Proxy["Generated Proxy Loader"]
Proxy -->|detects CPU at runtime| Pick["Fastest compatible variant"]
This means a single distributed binary can automatically take advantage of a newer CPU's instruction set when available, and fall back safely on older hardware, without ever being recompiled.
Measuring the payoff
PhoenixHPCProxy's own test suite compares, at several compiler optimization levels, the performance of a naive implementation, a hand-vectorized one, and one accessed through the generated proxy. This validates that the dispatch mechanism adds effectively no overhead compared to calling the optimized function directly. For checking that a given function actually gets vectorized by the compiler in the first place, see PhoenixMaqao on the Benchmarking page.
Why this matters
- Portability without compromise: the same binary reaches close-to-optimal performance across a range of CPU generations.
- Less boilerplate, fewer mistakes: alignment, compiler hints, and cache-friendly reshaping are handled by well-tested helpers instead of being reimplemented in every project.
- Consistent measurement: PhoenixHardware's timing and profiling primitives share the same philosophy as PhoenixMicroBenchmark, keeping performance work consistent across the Phoenix2 ecosystem.