diff --git a/analysis/fig_milestones.png b/analysis/fig_milestones.png new file mode 100644 index 0000000..eb03123 Binary files /dev/null and b/analysis/fig_milestones.png differ diff --git a/analysis/plots.py b/analysis/plots.py index 0669f1f..9895710 100644 --- a/analysis/plots.py +++ b/analysis/plots.py @@ -59,7 +59,7 @@ plt.savefig("analysis/fig_tracks.png", dpi=170, bbox_inches="tight") bars = [ ("baseline", "variant_baseline"), ("AVX2 (double)", "variant_inlining_7_SoA_AVX2"), - ("AVX-512 (double)", "variant_avx512_1"), + ("AVX-512 (double)", "variant_AVX512_1"), ("+ polyexp", "variant_inlining_8_SoA_AVX512_polyexp_moreavx"), ("+ float", "variant_float_inlining_SoA_AVX512_polyexp_moreavx"), ("+ rsqrt & ILP", "variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt"), @@ -101,7 +101,7 @@ for pk,t in [(SCALAR,"scalar (4)"),(PEAK_D,"double (16)"),(PEAK_F,"float (32)")] I5000 = df[(df.variant=="variant_baseline") & (df.n==5000)]["I"].values[0] pts = [ ("variant_baseline", "#7a7a7a"), - ("variant_avx512_1", "#2f9e8f"), + ("variant_AVX512_1", "#2f9e8f"), ("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "#e08a1e"), ("variant_float_inlining_SoA_AVX512_polyexp_moreavx", "#c0436a"), ("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt", maroon), @@ -140,5 +140,46 @@ ax.legend(frameon=False, fontsize=10) plt.tight_layout() plt.savefig("analysis/fig_exp.png", dpi=170, bbox_inches="tight") +# plot 5: the milestone ladder as lines (this is plot0 + line1, line2, ... for the doc). +# one line per relevant optimization step across n, baseline at the bottom. +# unlike fig_tracks this includes the scalar steps so you can see where each jump comes from. +milestones = [ + ("variant_baseline", "baseline", "#7a7a7a", "-"), + ("variant_cutoff_callsite", "+ cutoff (best scalar)", "#9e8f2f", "-"), + ("variant_inlining_7_SoA_AVX2", "AVX2 (double)", "#3b6fb0", "--"), + ("variant_inlining_7_SoA_AVX2_polyexp", "+ polyexp (double)", "#2f9e8f", "--"), + ("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "AVX-512 (double)", "#2f5d9e", "-"), + ("variant_float_inlining_SoA_AVX2_polyexp", "float AVX2 + polyexp", "#e58aa6", "--"), + ("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "float AVX2 + rsqrt", "#e08a1e", "--"), + ("variant_float_inlining_SoA_AVX512_polyexp_moreavx", "float AVX-512", "#c0436a", "-"), + ("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt","float AVX-512 + rsqrt + ILP (best)", maroon, "-"), +] + +fig, ax = plt.subplots(figsize=(9.5,5.2)) +for v,lab,c,ls in milestones: + x,y = getline(v) + if len(x) == 0: # variant not in results.csv -> skip instead of crashing + print("skip (not in csv):", v) + continue + ax.plot(x, y, ls, color=c, marker="o", ms=4, lw=2.4 if c==maroon else 1.7, label=lab) + +for yv,t in [(PEAK_D,"double peak (16)"), (PEAK_F,"float peak (32)")]: + ax.axhline(yv, ls=":", color="#999", lw=1.2) + ax.text(52, yv*1.03, t, fontsize=9.5, color="#888") + +ax.set_xscale("log"); ax.set_yscale("log") +ax.set_xticks([50,100,200,500,1000,2000,5000,10000]) +ax.set_xticklabels(["50","100","200","500","1k","2k","5k","10k"]) +ax.set_yticks([1,2,4,8,16,32]); ax.set_yticklabels(["1","2","4","8","16","32"]) +ax.set_xlim(46,13000); ax.set_ylim(0.8,40) +ax.set_xlabel("number of pedestrians n") +ax.set_ylabel("performance [flops/cycle]") +ax.grid(color="#e9e9e9") +ax.spines["top"].set_visible(False); ax.spines["right"].set_visible(False) +ax.legend(fontsize=8.5, loc="upper left", ncol=2, framealpha=.95) +plt.tight_layout() +plt.savefig("analysis/fig_milestones.png", dpi=170, bbox_inches="tight") + + #plt.show() print("done") \ No newline at end of file diff --git a/scripts/test_novec.sh b/scripts/test_novec.sh new file mode 100644 index 0000000..746a49f --- /dev/null +++ b/scripts/test_novec.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# milestone 10: find the best NON-vectorized scalar variant. +# we compile each scalar (double, no-intrinsics) variant with vectorization +# turned off (-fno-tree-vectorize -fno-tree-slp-vectorize) so -march=native +# can't sneak SIMD in, then time them on a small input. +# +# small on purpose: N=500, 200 iters -> each build+run is a couple seconds. +# compare T(n) [cycles] to pick the fastest. flops/cycle is printed too but +# don't rank by it: the symmetry variants do half the work, so their +# flops/cycle looks low even when the runtime is good. +# +# run from the repo root: bash scripts/test_novec.sh + +set -e + +CXX="${CXX:-$(command -v g++ || command -v clang++)}" +N=500 +ITER=200 +NOVEC="-fno-tree-vectorize -fno-tree-slp-vectorize" +FLAGS="-O3 -march=native -std=c++23 -flto $NOVEC -DBENCHMARK=1 -DFLOPCOUNT=0 -DRANDOM_SEED=0 -DROOFLINE=0" + +# variant:main_file . symmetry variants need main-symmetry.cpp (halved flop formula), +# everything else uses main.cpp. only double, no-AVX variants here. +CANDIDATES=( + "baseline:main.cpp" + "inlining_7:main.cpp" + "inlining_8:main.cpp" + "cutoff:main.cpp" + "cutoff_callsite:main.cpp" + "branchless:main.cpp" + "branchless_sqrtfree:main.cpp" + "lut_exp:main.cpp" + "inlining_7_SoA:main.cpp" + "inlining_7_symmetry_2:main-symmetry.cpp" + "inlining_7_symmetry_2_ilp_2:main-symmetry.cpp" +) + +echo "compiler: $CXX" +echo "flags: $FLAGS" +echo "input: N=$N ITER=$ITER" +echo "" +printf "%-34s %18s %14s\n" "variant" "cycles" "flops/cycle" +printf "%-34s %18s %14s\n" "-------" "------" "-----------" + +for entry in "${CANDIDATES[@]}"; do + v="${entry%%:*}" + main="${entry##*:}" + src="variants/variant_${v}.cpp" + + if [ ! -f "$src" ]; then + printf "%-34s %18s\n" "$v" "(no source, skipped)" + continue + fi + + bin="novec_${v}" + $CXX "$main" "$src" -o "$bin" $FLAGS 2>/dev/null + + out="$(./"$bin" "$N" "$ITER")" + cyc="$(echo "$out" | grep 'T(n):' | grep -oE '[0-9.]+')" + fpc="$(echo "$out" | grep 'P(n):' | grep -oE '[0-9.]+')" + + printf "%-34s %18s %14s\n" "$v" "$cyc" "$fpc" + rm -f "$bin" +done + +echo "" +echo "lowest cycles = best non-vectorized variant. report that number for milestone 10."