kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 42c5ca3a53352e4fd3148c52257cb3a02a911be2
parent 67344f35ae4b8850515b5741595103ef9bbdd65b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 27 May 2026 11:38:43 -0700

bench: replace FAST flag with per-tool skip flags; add compare script

- Drop the FAST/slow binary mode in opt_bench.sh.  Default is now always
  the quick subset (O0+O1, 5 benches, 3 repeats each), with the full set
  described in a comment for easy opt-in via CFREE_OPT_BENCHES.
- Add SKIP_GCC / SKIP_CLANG / SKIP_MIR flags so individual reference
  tools can be disabled without touching anything else.  Default keeps
  gcc and MIR, skips clang (slow on macOS, rarely useful for quick runs).
- Add CFREE_OPT_BENCH_ARG_OVERRIDE to pin a single bench arg across all
  benchmarks (useful for quick smoke runs with small N).
- Add scripts/opt_bench_compare.py: reads the CSV and prints a
  per-benchmark + geomean table of cfree vs the gcc baseline.  The bench
  script auto-invokes it at the end when present.

Diffstat:
Mscripts/opt_bench.sh | 43+++++++++++++++++++++++++------------------
Ascripts/opt_bench_compare.py | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 207 insertions(+), 18 deletions(-)

diff --git a/scripts/opt_bench.sh b/scripts/opt_bench.sh @@ -10,22 +10,25 @@ GCC="${GCC:-gcc-15}" OUT_DIR="${CFREE_OPT_BENCH_OUT:-$ROOT/build/bench/opt}" CFREE_SYSROOT="${CFREE_OPT_BENCH_SYSROOT:-}" -FAST="${CFREE_OPT_BENCH_FAST:-0}" -if [ "$FAST" = "1" ]; then - DEFAULT_LEVELS="1" - DEFAULT_BENCHES="array hash hash2 matrix sieve" - DEFAULT_COMPILE_REPEATS="3" - DEFAULT_RUN_REPEATS="3" -else - DEFAULT_LEVELS="0 1 2" - DEFAULT_BENCHES="array binary-trees except funnkuch-reduce hash hash2 heapsort lists matrix method-call mandelbrot nbody sieve spectral-norm strcat" - DEFAULT_COMPILE_REPEATS="1" - DEFAULT_RUN_REPEATS="3" -fi +# Full benchmark set (override with CFREE_OPT_BENCHES to use it): +# array binary-trees except funnkuch-reduce hash hash2 heapsort lists matrix +# method-call mandelbrot nbody sieve spectral-norm strcat +# `except` in particular runs for many seconds at -O0, which is why the default +# below is a small, quick subset at O0+O1 (skip the heavy O2 sweep). +DEFAULT_LEVELS="0 1" +DEFAULT_BENCHES="array hash hash2 matrix sieve" +DEFAULT_COMPILE_REPEATS="3" +DEFAULT_RUN_REPEATS="3" LEVELS="${CFREE_OPT_BENCH_LEVELS:-$DEFAULT_LEVELS}" BENCHES="${CFREE_OPT_BENCHES:-$DEFAULT_BENCHES}" COMPILE_REPEATS="${CFREE_OPT_BENCH_COMPILE_REPEATS:-$DEFAULT_COMPILE_REPEATS}" RUN_REPEATS="${CFREE_OPT_BENCH_RUN_REPEATS:-$DEFAULT_RUN_REPEATS}" +# Per-tool skip flags. By default keep gcc (the baseline) and skip clang. +# Override individually, e.g. CFREE_OPT_BENCH_SKIP_MIR=1 or +# CFREE_OPT_BENCH_SKIP_CLANG=0. +SKIP_GCC="${CFREE_OPT_BENCH_SKIP_GCC:-0}" +SKIP_CLANG="${CFREE_OPT_BENCH_SKIP_CLANG:-1}" +SKIP_MIR="${CFREE_OPT_BENCH_SKIP_MIR:-0}" MIR_MAKE="${MIR_MAKE:-}" case "$(uname -s 2>/dev/null || true)" in @@ -493,23 +496,27 @@ printf 'opt-bench: compile repeats=%s run repeats=%s\n' "$COMPILE_REPEATS" "$RUN for bench in $BENCHES; do src="$BENCH_DIR/$bench.c" expect="$BENCH_DIR/$bench.expect" - arg_line="$(read_arg_file "$BENCH_DIR/$bench.arg")" + arg_line="${CFREE_OPT_BENCH_ARG_OVERRIDE:-$(read_arg_file "$BENCH_DIR/$bench.arg")}" if [ ! -f "$src" ]; then printf 'opt-bench: skipping missing benchmark source: %s\n' "$src" >&2 continue fi printf '+++++ %s %s +++++\n' "$bench" "$arg_line" for opt in $LEVELS; do - if [ "$FAST" != "1" ]; then - bench_native_tool "$bench" "$(tool_label "$GCC")" "$GCC" "$opt" "$src" "$expect" "$arg_line" - bench_native_tool "$bench" "$(tool_label "$CLANG")" "$CLANG" "$opt" "$src" "$expect" "$arg_line" - fi + [ "$SKIP_GCC" != "1" ] && bench_native_tool "$bench" "$(tool_label "$GCC")" "$GCC" "$opt" "$src" "$expect" "$arg_line" + [ "$SKIP_CLANG" != "1" ] && bench_native_tool "$bench" "$(tool_label "$CLANG")" "$CLANG" "$opt" "$src" "$expect" "$arg_line" bench_native_tool "$bench" "cfree" "$CFREE cc" "$opt" "$src" "$expect" "$arg_line" bench_cfree_run "$bench" "$opt" "$src" "$expect" "$arg_line" - bench_mir "$bench" "$opt" "$src" "$expect" "$arg_line" + [ "$SKIP_MIR" != "1" ] && bench_mir "$bench" "$opt" "$src" "$expect" "$arg_line" done done write_summary printf 'opt-bench: wrote %s\n' "$CSV" printf 'opt-bench: wrote %s\n' "$SUMMARY" + +COMPARE_SCRIPT="$ROOT/scripts/opt_bench_compare.py" +if [ -f "$COMPARE_SCRIPT" ]; then + printf '\n' + python3 "$COMPARE_SCRIPT" "$CSV" || true +fi diff --git a/scripts/opt_bench_compare.py b/scripts/opt_bench_compare.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Compare cfree tools vs a baseline (default: gcc-15 -O0). + +Usage: + python3 scripts/opt_bench_compare.py [results.csv] + python3 scripts/opt_bench_compare.py results.csv --base-tool gcc-15 --base-opt 0 +""" +import csv +import math +import os +import sys + + +def fnum(v): + try: + return float(v) + except (TypeError, ValueError): + return None + + +def speedup_label(s): + if s is None: + return "NA" + if abs(s - 1.0) < 0.03: + return "~tied" + return f"{s:.2f}x faster" if s > 1 else f"{1/s:.2f}x slower" + + +def compare(val, base): + """Return speedup ratio: >1 means val is faster (lower time) than base.""" + if not val or not base or base <= 0 or val <= 0: + return None + return base / val + + +def geo(xs): + xs = [x for x in xs if x and x > 0] + if not xs: + return None + return math.exp(sum(math.log(x) for x in xs) / len(xs)) + + +def compile_total(r): + cm = fnum(r.get("compile_ms")) + cg = fnum(r.get("codegen_ms")) or 0.0 + return (cm + cg) if cm is not None else None + + +def main(): + args = sys.argv[1:] + csv_path = None + base_tool_arg = None + base_opt = "0" + i = 0 + while i < len(args): + if args[i] == "--base-tool" and i + 1 < len(args): + base_tool_arg = args[i + 1] + i += 2 + elif args[i] == "--base-opt" and i + 1 < len(args): + base_opt = args[i + 1] + i += 2 + else: + csv_path = args[i] + i += 1 + + if csv_path is None: + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + csv_path = os.path.join(root, "build", "bench", "opt", "results.csv") + + if not os.path.exists(csv_path): + sys.exit(f"compare: CSV not found: {csv_path}") + + with open(csv_path, newline="") as f: + ok = [r for r in csv.DictReader(f) if r["status"] == "OK"] + + if not ok: + sys.exit("compare: no OK rows in CSV") + + # Find base tool + base_tool = base_tool_arg + if not base_tool: + for prefix in ("gcc-15", "gcc"): + base_tool = next((r["tool"] for r in ok if r["tool"].startswith(prefix)), None) + if base_tool: + break + if not base_tool: + sys.exit("compare: no gcc tool found; use --base-tool TOOL") + + # Baseline map: bench -> (compile_total_ms, runtime_ms) + baseline = {} + for r in ok: + if r["tool"] == base_tool and r["opt"] == base_opt: + baseline[r["bench"]] = (compile_total(r), fnum(r["runtime_ms"])) + + if not baseline: + sys.exit(f"compare: no rows for tool={base_tool} opt={base_opt}") + + idx = {(r["tool"], r["opt"], r["bench"]): r for r in ok} + all_benches = sorted(baseline) + all_opts = sorted( + {r["opt"] for r in ok}, + key=lambda x: (int(x) if x.isdigit() else 99, x), + ) + + # Show order: base row first, then cfree/cfree-run at each opt level + show = [(base_tool, base_opt)] + seen = {(base_tool, base_opt)} + for t in ("cfree", "cfree-run"): + for o in all_opts: + key = (t, o) + if key not in seen and any((t, o, b) in idx for b in all_benches): + show.append(key) + seen.add(key) + + print(f"cfree vs {base_tool} -O{base_opt} [{os.path.basename(csv_path)}]") + print() + + # Per-benchmark table + bench_label = all_benches[0] if len(all_benches) == 1 else f"{len(all_benches)} benches" + print(f"Per-benchmark: {bench_label}") + print() + hdr = ( + f"{'bench':<16} {'tool':<12} opt " + f"{'compile_ms':>10} {'compile vs base':>16} " + f"{'runtime_ms':>10} {'runtime vs base':>16}" + ) + print(hdr) + print("-" * len(hdr)) + + for bench in all_benches: + bcm, brt = baseline[bench] + first = True + for tool, opt in show: + r = idx.get((tool, opt, bench)) + if r is None: + continue + ct = compile_total(r) + rt = fnum(r["runtime_ms"]) + is_base = tool == base_tool and opt == base_opt + cl = "baseline" if is_base else speedup_label(compare(ct, bcm)) + rl = "baseline" if is_base else speedup_label(compare(rt, brt)) + bench_col = bench if first else "" + first = False + ct_s = f"{ct:.1f}" if ct is not None else "NA" + rt_s = f"{rt:.1f}" if rt is not None else "NA" + print( + f"{bench_col:<16} {tool:<12} O{opt:<2} " + f"{ct_s:>10} {cl:>16} " + f"{rt_s:>10} {rl:>16}" + ) + print() + + if len(all_benches) <= 1: + return + + # Geomean summary + print(f"Geomean ({len(all_benches)} benches)") + print() + hdr2 = f"{'tool':<12} opt {'compile speedup':>16} {'runtime speedup':>16}" + print(hdr2) + print("-" * len(hdr2)) + for tool, opt in show: + crs, rrs = [], [] + for bench in all_benches: + bcm, brt = baseline.get(bench, (None, None)) + r = idx.get((tool, opt, bench)) + if r is None: + continue + s = compare(compile_total(r), bcm) + if s is not None: + crs.append(s) + s = compare(fnum(r["runtime_ms"]), brt) + if s is not None: + rrs.append(s) + is_base = tool == base_tool and opt == base_opt + cl = "baseline" if is_base else speedup_label(geo(crs)) + rl = "baseline" if is_base else speedup_label(geo(rrs)) + print(f"{tool:<12} O{opt:<2} {cl:>16} {rl:>16}") + + +if __name__ == "__main__": + main()