This project is a compact, benchmark-driven study of explicit SIMD in modern C++.
- Eight progressively more demanding scalar/SIMD algorithms.
- Explicit load–compute–store loops with safe scalar tails.
- Reductions, masks, FMA, sliding windows, softmax, and convolution.
- Independent correctness checks and isolated executables.
- Measured SIMD gains from negligible to about 10×, depending on the bottleneck.
- Final-binary inspection confirms the generated AVX-512 instructions.
| Example | Description |
|---|---|
| 1. Element-wise array addition | Adds arrays element by element; introduces SIMD loops. |
| 2. Sum reduction | Sums lanes, then horizontally reduces the accumulator. |
| 3. Upper-bound clamp | Clamps values using comparisons and conditional masks. |
| 4. Count above threshold | Counts threshold matches with masks and popcount. |
| 5. Numerically stable softmax | Computes stable softmax with vector reductions. |
| 6. FMA and dot product | Contrasts memory-bound FMA with compute-bound dot product. |
| 7. Horizontal image blur | Blurs rows using overlapping loads and scalar borders. |
| 8. 1D mathematical convolution | Convolves with reversed kernels and vectorized outputs. |
The 1D kernels used 16,777,216 elements and softmax used 4,194,304 elements. Values are median speedups from three trials; speedup means scalar time divided by SIMD time. The softmax benchmark uses a smaller input because the current float normalization accumulation loses validation accuracy at much larger sizes.
Results are from an Intel Xeon Platinum 8480+ on one exclusive MN5 node and one
pinned CPU core. Scalar targets disable compiler vectorization; SIMD targets
use explicit std::experimental::simd with normal optimization.
| Kernel | GCC | icpx |
|---|---|---|
| Element-wise addition | 1.17× | 1.54× |
| Sum reduction | 5.14× | 5.15× |
| Upper-bound clamp | 7.85× | 10.29× |
| Count above threshold | 4.91× | 4.19× |
| Softmax | 1.64× | 4.43× |
| Memory-bound FMA | 1.02× | 0.99× |
| Dot product | 1.62× | 4.03× |
| Horizontal blur | TBD | TBD |
| 1D convolution | TBD | TBD |
Among exercises 1–6, reductions, masks, and dot products benefit most. Addition and memory FMA are limited mainly by memory traffic.
The normal icpx softmax build also auto-vectorizes the scalar exponential
loop through Intel SVML. With compiler auto-vectorization disabled, its softmax
speedup was approximately 1.44×.
RISC-V binaries were cross-compiled with conda-forge GCC 16.2 and executed on a
Banana Pi F3 through the bananaf3 queue. The target provides RVV 1.0 with a
256-bit VLEN (vlenb_bytes=32). GCC/libstdc++ reports one lane for
native_simd<float> on this target, so the comparison uses fixed-size SIMD
widths of four and eight lanes.
| Kernel | VL=4 speedup |
VL=8 speedup |
|---|---|---|
| Element-wise addition | 1.43× | 1.43× |
| Sum reduction | 1.88× | 4.80× |
| Upper-bound clamp | 4.29× | 2.94× |
| Count above threshold | 1.33× | 2.04× |
| Softmax | 1.23× | 1.22× |
| Memory-bound FMA | 1.48× | 1.55× |
| Dot product | 1.30× | 1.94× |
| Horizontal blur | TBD | TBD |
| 1D convolution | TBD | TBD |
The VL=4 and VL=8 values select software vector widths; they do not change
the hardware VLEN. The count_above SIMD function contained no RVV
instructions in the final binaries, so its measured gain came from scalar
unrolling rather than genuine vector execution.
The current build targets x86-64 Linux on MareNostrum 5:
- Intel Xeon Platinum 8480+ with AVX-512;
- GCC 14.1.0 or Intel
icpx2025.2; - C++2b,
-O3, and-march=native; native_simd<float>::size()is typically 16 on this CPU.
Use a clean module environment when switching compilers. Both builds produce the same executable names.
GCC build and run commands
module purge
module load gcc/14.1.0_binutils241
make clean
make drivers
./build/01_add_scalar --size 16777216 --repetitions 10
./build/01_add_simd --size 16777216 --repetitions 10Intel icpx build and run commands
module purge
module load intel/2025.2
make clean
make CXX=icpx drivers
./build/01_add_scalar --size 16777216 --repetitions 10
./build/01_add_simd --size 16777216 --repetitions 10Build subsets or run all default drivers with:
Make targets
make scalar
make simd
make runA driver can write a combined scalar/SIMD CSV:
Benchmark command
scripts/benchmark.sh 02_sum \
--size 16777216 \
--repetitions 10 \
--output results/02_sum.csvInspect the final executable after linking:
Inspection commands
objdump -d -C build/01_add_simd | grep -E 'vaddps|vmov'
objdump -d -C build/03_clamp_simd | grep -E 'vcmpps|vblend|vmov'
objdump -d -C build/06_fma_simd | grep -E 'vfmadd|vmov'- SIMD processes several values per instruction, not the whole input at once.
- Explicit SIMD is built from vector loads, lane-wise operations, stores, and a scalar tail.
- Reductions require partial lane accumulators and horizontal reduction.
- Compiler choice and generated instructions affect measured performance.
- Memory bandwidth can dominate even when SIMD computation is available.
- Correctness validation, benchmarking, and binary inspection must be done together.
This project is licensed under the MIT License. See LICENSE for details.