updated create csv and updated plots

This commit is contained in:
Linus Baumberger
2026-06-03 22:36:44 +02:00
committed by [Quy Anh] «Elliot» Nguyen
parent daf75faaf3
commit ac9ef23559
7 changed files with 100 additions and 38 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 102 KiB

+16 -8
View File
@@ -27,6 +27,14 @@ def num(pattern, text):
#min cycles is the standard way to kill the OS/turbo noise.
best = {}
# flop counter is uint32 and wraps at n=10000 (72*n^2+74*n = 7.2e9 > 2^32), so the logged
# W(n)/I(n) are garbage there. compute work ourselves; matches the good logs exactly.
# symmetry variants do half the pairs -> 36 n^2 + 110 n, everything else 72 n^2 + 74 n.
def flops_per_iter(variant, n):
if "symmetry" in variant:
return 36 * n * n + 110 * n
return 72 * n * n + 74 * n
for variant in sorted(os.listdir(ROOT)):
logdir = os.path.join(ROOT, variant, "x86")
if not os.path.isdir(logdir):
@@ -48,20 +56,20 @@ for variant in sorted(os.listdir(ROOT)):
cycles = num(r"T\(n\):\s*([\d.]+)", block)
if cycles is None:
continue
W = num(r"W\(n\):\s*([\d.]+)", block)
key = (variant, n)
if key not in best or cycles < best[key][2]:
best[key] = (variant, n, cycles, n_iter, W,
num(r"Q\(n\) per iteration:\s*([\d.]+)", block),
num(r"I\(n\):\s*([\d.]+)", block))
if key not in best or cycles < best[key][0]:
best[key] = (cycles, n_iter,
num(r"Q\(n\) per iteration:\s*([\d.]+)", block))
with open(OUT, "w", newline="") as f:
w = csv.writer(f)
w.writerow(["variant", "n", "n_iter", "W", "Q_per_iter", "I", "cycles", "flops_per_cycle"])
for variant, n in sorted(best):
_, _, cycles, n_iter, W, q, I = best[(variant, n)]
cycles, n_iter, q = best[(variant, n)]
w_iter = flops_per_iter(variant, n)
W = w_iter * n_iter
I = w_iter / q
fc = W / cycles
w.writerow([variant, n, n_iter, W, q, I, round(cycles, 1), round(fc, 3)])
w.writerow([variant, n, n_iter, W, q, round(I, 2), round(cycles, 1), round(fc, 3)])
print("wrote", OUT, "with", len(best), "rows")
+83 -29
View File
@@ -81,13 +81,14 @@ base = get("variant_baseline")
# plot 1: the two precision tracks (this is the main results plot)
# double tops out ~7, float goes to 24. also shows the avx2-float stuff already does most of the work
tracks = [
("variant_inlining_7_SoA_AVX2", "double, AVX2", "#9bbbe0", "--"),
("variant_baseline", "baseline (scalar)", "#7a7a7a", "-"),
("variant_AVX512_1", "double, AVX-512 ", "#4f86d6", "-"),
("variant_inlining_7_SoA_AVX2", "double, AVX-256", "#9bbbe0", "--"),
("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "double, AVX-512 + polyexp", "#2f5d9e", "-"),
("variant_float_inlining_SoA_AVX2_polyexp", "float, AVX2 + polyexp", "#e58aa6", "--"),
("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "float, AVX2 + polyexp + rsqrt", "#d05a82", "--"),
("variant_float_inlining_SoA_AVX2_polyexp", "float, AVX-256 + polyexp", "#e58aa6", "--"),
("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "float, AVX-256 + polyexp + rsqrt", "#d05a82", "--"),
("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt", "float, AVX-512 + rsqrt + ILP (best)", maroon, "-"),
]
fig, ax = plt.subplots(figsize=(9,4.7))
ends = []
for v,lab,c,ls in tracks:
@@ -144,43 +145,53 @@ plt.tight_layout()
plt.savefig("analysis/fig_ladder.png", dpi=170, bbox_inches="tight")
# plot3 roofline
# every variant has the same I at a given n (W and Q dont change between variants), so the
# points just stack vertically. we're miles to the right of the ridge -> compute bound.
fig,ax = plt.subplots(figsize=(9,4.7))
bw = 60 # bytes/cycle, ~120 GB/s lpddr5x / 2GHz. rough
xx = np.logspace(-1, 3.6, 200)
ax.plot(xx, np.minimum(bw*xx, PEAK_F), color="#cccccc", lw=1.2)
# plot 3: roofline (n = 5000). same 7 variants as fig_tracks, same colors, inline labels.
# float variants move HALF the bytes -> 2x the operational intensity of the double ones
# (I_double ≈ 2.8k, I_float ≈ 5.6k), so each point uses its OWN I.
# memory roof from a measured single-thread read: sysbench 37618 MiB/s -> ~19.7 bytes/cycle @ 2 GHz.
def getI(v, n=5000):
return df[(df.variant==v) & (df.n==n)]["I"].values[0]
bw = 37618.01 * 1024**2 / 2e9 # MiB/s -> bytes/cycle at 2 GHz (≈ 19.72)
# (variant, label, color, group) group "d" = double column, "f" = float column
roof = [
("variant_baseline", "baseline (scalar)", "#7a7a7a", "d"),
("variant_inlining_7_SoA_AVX2", "double, AVX-256", "#9bbbe0", "d"),
("variant_AVX512_1", "double, AVX-512 ", "#4f86d6", "d"),
("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "double, AVX-512 + polyexp", "#2f5d9e", "d"),
("variant_float_inlining_SoA_AVX2_polyexp", "float, AVX-256 + polyexp", "#e58aa6", "f"),
("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "float, AVX2 + polyexp + rsqrt", "#d05a82", "f"),
("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt", "float, AVX-512 + rsqrt + ILP (best)", maroon, "f"),
]
LABX = {"d": 2400, "f": 4900} # label right-edge sits just left of each column
fig, ax = plt.subplots(figsize=(9.5,5.0))
xx = np.logspace(-1, 4.2, 200)
ax.plot(xx, np.minimum(bw*xx, PEAK_F), color="#bbbbbb", lw=1.4)
ax.text(0.115, 0.85, "mem roof: DRAM read ≈ 37.6 GiB/s (≈19.7 B/cycle)", fontsize=8.3, color="#9a9a9a", ha="left")
for pk,t in [(SCALAR,"scalar (4)"), (PEAK_D,"double (16)"), (PEAK_F,"float (32)")]:
ax.axhline(pk, ls="--", color="#999", lw=1.1)
ax.text(0.11, pk*1.04, t+" peak", fontsize=9.5, color="#888")
ax.axhline(pk, ls="--", color="#aaaaaa", lw=1.0)
ax.text(0.11, pk*1.05, t+" peak", fontsize=9, color="#888")
I5000 = df[(df.variant=="variant_baseline") & (df.n==5000)]["I"].values[0]
pts = [
("variant_baseline", "#7a7a7a"),
("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),
]
for v,c in pts:
ax.plot(I5000, get(v), "o", ms=9, color=c, mec="white", mew=1)
for v,lab,c,grp in roof:
x,y = getI(v), get(v)
ax.plot(x, y, "o", ms=10, color=c, mec="white", mew=1.4, zorder=5)
t = ax.text(LABX[grp], y, lab, color=c, fontsize=8, fontweight="bold", ha="right", va="center", zorder=6)
t.set_path_effects([pe.withStroke(linewidth=2.5, foreground="white")])
ax.text(I5000, 1.3, "n=5000", fontsize=10, color="#555")
ax.text(2812, 0.74, "I ≈ 2.8k", fontsize=8, color="#777", ha="center", va="top")
ax.text(5625, 0.74, "I ≈ 5.6k", fontsize=8, color="#777", ha="center", va="top")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlim(0.1, 6500); ax.set_ylim(0.7, 45)
ax.set_xlim(0.1, 16000); ax.set_ylim(0.6, 46)
ax.set_xlabel("operational intensity I [flops/byte]")
ax.set_ylabel("performance [flops/cycle]")
#ax.set_title("roofline: deeply compute-bound (I grows with n; work O(n^2), data O(n))", loc="left", fontsize=12)
ax.grid(color="#ededed")
ax.spines["top"].set_visible(False); ax.spines["right"].set_visible(False)
plt.tight_layout()
plt.savefig("analysis/fig_roofline.png", dpi=170, bbox_inches="tight")
# plot 4: exp vs the polynomial approx (just to show its basically exact)
x = np.linspace(-6, 0, 300)
ex = np.exp(x)
@@ -205,7 +216,7 @@ plt.savefig("analysis/fig_exp.png", dpi=170, bbox_inches="tight")
# "above" or "below" -> alternate the crowded float labels above/below their line.
milestones = [
("variant_baseline", "baseline", "#7a7a7a", "-", None, "above"),
("variant_cutoff_callsite", "baseline + cutoff (best scalar)", "#9e8f2f", "-", None, "below"),
#("variant_cutoff_callsite", "baseline + cutoff (best scalar)", "#9e8f2f", "-", None, "below"),
("variant_inlining_7_SoA_AVX2", "AVX2 (double)", "#3b6fb0", "--", None, "above"),
("variant_inlining_7_SoA_AVX2_polyexp", "AVX2 + polyexp (double)", "#2f9e8f", "--", None, "below"),
("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "AVX-512 (double)", "#2f5d9e", "-", None, "below"),
@@ -245,3 +256,46 @@ plt.savefig("analysis/fig_milestones.png", dpi=170, bbox_inches="tight")
#plt.show()
print("done")
# plot 6: wall-clock speedup over baseline (runtime, not performance).
# the fair view for optimizations that change the flop count -- cutoff skips ~40% of the
# inner loop, symmetry does half the pairs -- so flops/cycle misrepresents them. T_base /
# T_variant compares time-to-solve the same n regardless of how many flops each one does.
# curated to the milestones that carry the story + symmetry as the cautionary line.
basec = df[df.variant == "variant_baseline"].set_index("n")["cycles"].to_dict()
def speedline(v):
s = df[df.variant == v].sort_values("n")
ns = [n for n in s.n.values if n in basec] # baseline has no n=10000 run
sp = [basec[n] / s[s.n == n]["cycles"].values[0] for n in ns]
return np.array(ns, float), np.array(sp, float)
# (variant, label, color, linestyle, xpos|None, side)
speed = [
("variant_cutoff_callsite", "scalar + cutoff (approx)", "#9e8f2f", "-", 260, "above"),
("variant_inlining_7_symmetry_2", "symmetry (half the pairs)", "#cf2b2b", "-", 320, "below"),
("variant_inlining_7_SoA_AVX2", "double, AVX2", "#9bbbe0", "--", 650, "below"),
("variant_inlining_8_SoA_AVX512_polyexp_moreavx", "double, AVX-512 + polyexp", "#2f5d9e", "-", 430, "above"),
("variant_float_inlining_SoA_AVX2_polyexp_rsqrt_loopopt", "float, AVX2 + polyexp + rsqrt", "#d05a82", "--", 950, "below"),
("variant_float_inlining_SoA_AVX512_polyexp_moreavx_ilp_2_rsqrt_morersqrt", "float, AVX-512 + rsqrt + ILP (best)", maroon, "-", 680, "above"),
]
fig, ax = plt.subplots(figsize=(9,4.7))
ends = []
for v,lab,c,ls,xpos,side in speed:
x,y = speedline(v)
if len(x) == 0:
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.8)
ends.append((x, y, lab, c, xpos, side))
ax.axhline(1, ls=":", color="#999", lw=1.3)
ax.text(52, 1.03, "baseline (1x)", fontsize=9, color="#888")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xticks([50,100,200,500,1000,2000,5000]); ax.set_xticklabels(["50","100","200","500","1k","2k","5k"])
ax.set_yticks([1,2,5,10,20]); ax.set_yticklabels(["1x","2x","5x","10x","20x"])
ax.set_xlim(46,7000); ax.set_ylim(0.8,28)
ax.set_xlabel("number of pedestrians n")
ax.set_ylabel("speedup over baseline (wall-clock cycles)")
ax.grid(color="#e9e9e9")
ax.spines["top"].set_visible(False); ax.spines["right"].set_visible(False)
plt.tight_layout()
label_lines_above(ax, ends, fontsize=7.5, dy_frac=0.02, halo=2)
plt.savefig("analysis/fig_speedup.png", dpi=170, bbox_inches="tight")