#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)" BUILD_DIR="${SLIME_BENCH_BUILD_DIR:-/tmp/slime-bench-build}" OUT_DIR="${SLIME_BENCH_OUT_DIR:-/tmp/slime-bench-results}" CXX="${CXX:-g++}" NVCC="${NVCC:-nvcc}" CXXFLAGS="${CXXFLAGS:--O3 -std=c++17 -march=native -pthread -fwrapv}" NVCCFLAGS="${NVCCFLAGS:--O3 -std=c++17 -Xcompiler=-fwrapv}" RUNS=3 WARMUPS=1 DO_CPU=1 DO_CUDA=1 KEEP_OUTPUT=0 SEARCH_ARGS=() usage() { cat <<'EOF' Usage: bash codex/bench_wsl.sh [bench options] [-- common slime args] Bench options: --runs Timed runs per binary (default: 3) --warmups Untimed warmup runs per binary (default: 1) --skip-cpu Do not build/run CPU binaries --skip-cuda Do not build/run CUDA binaries --build-dir Build directory (default: /tmp/slime-bench-build) --out-dir Output directory (default: /tmp/slime-bench-results) --keep-output Keep per-run stdout/stderr logs --help Show this help Common slime args are passed to all binaries. Use only options supported by both the original programs and the codex programs: -s, -e, -n, -d, -r. Default common args: -s 0 -e 100000 -n 3 -r 60 Examples: bash codex/bench_wsl.sh bash codex/bench_wsl.sh --runs 5 -- -s 0 -e 1000000 -n 3 -r 100 bash codex/bench_wsl.sh --skip-cuda -- -s 0 -e 200000 -n 4 -r 80 Notes: * CPU comparison runs codex/slime_cpu with --all-locations to match the original CPU program, which scans every matching location. * CUDA comparison runs codex/slime_cuda in first-hit-per-seed mode to match the original CUDA program. * The original CPU program ignores non-zero -s while splitting thread ranges, so use -s 0 for fair CPU result counts. EOF } die() { echo "error: $*" >&2 exit 1 } need_value() { local name="$1" local value="${2:-}" [[ -n "$value" ]] || die "$name needs a value" } while [[ $# -gt 0 ]]; do case "$1" in --runs) need_value "$1" "${2:-}" RUNS="$2" shift 2 ;; --warmups) need_value "$1" "${2:-}" WARMUPS="$2" shift 2 ;; --skip-cpu) DO_CPU=0 shift ;; --skip-cuda) DO_CUDA=0 shift ;; --build-dir) need_value "$1" "${2:-}" BUILD_DIR="$2" shift 2 ;; --out-dir) need_value "$1" "${2:-}" OUT_DIR="$2" shift 2 ;; --keep-output) KEEP_OUTPUT=1 shift ;; --help) usage exit 0 ;; --) shift SEARCH_ARGS=("$@") break ;; *) SEARCH_ARGS+=("$1") shift ;; esac done [[ "$RUNS" =~ ^[0-9]+$ && "$RUNS" -gt 0 ]] || die "--runs must be a positive integer" [[ "$WARMUPS" =~ ^[0-9]+$ ]] || die "--warmups must be a non-negative integer" if [[ "${#SEARCH_ARGS[@]}" -eq 0 ]]; then SEARCH_ARGS=(-s 0 -e 10000000 -n 3 -r 60) fi START_SEED_ARG=0 for ((i = 0; i < ${#SEARCH_ARGS[@]}; ++i)); do if [[ "${SEARCH_ARGS[$i]}" == "-s" && $((i + 1)) -lt ${#SEARCH_ARGS[@]} ]]; then START_SEED_ARG="${SEARCH_ARGS[$((i + 1))]}" fi done mkdir -p "$BUILD_DIR" "$OUT_DIR" read -r -a CXXFLAGS_ARR <<< "$CXXFLAGS" read -r -a NVCCFLAGS_ARR <<< "$NVCCFLAGS" build_cpu() { command -v "$CXX" >/dev/null 2>&1 || die "cannot find C++ compiler '$CXX'" echo "Building CPU binaries with: $CXX $CXXFLAGS" "$CXX" "${CXXFLAGS_ARR[@]}" "$ROOT_DIR/slime.cpp" -o "$BUILD_DIR/user_cpu" "$CXX" "${CXXFLAGS_ARR[@]}" "$ROOT_DIR/codex/slime_cpu.cpp" -o "$BUILD_DIR/codex_cpu" } build_cuda() { if ! command -v "$NVCC" >/dev/null 2>&1; then echo "Skipping CUDA: cannot find '$NVCC'" >&2 DO_CUDA=0 return fi echo "Building CUDA binaries with: $NVCC $NVCCFLAGS" "$NVCC" "${NVCCFLAGS_ARR[@]}" "$ROOT_DIR/slime.cu" -o "$BUILD_DIR/user_cuda" "$NVCC" "${NVCCFLAGS_ARR[@]}" "$ROOT_DIR/codex/slime_cuda.cu" -o "$BUILD_DIR/codex_cuda" } extract_count() { local file="$1" local count count="$(sed -n 's/.*hits=\([0-9][0-9]*\).*/\1/p' "$file" | tail -n 1)" if [[ -z "$count" ]]; then count="$(sed -n 's/.*found \([0-9][0-9]*\) seeds.*/\1/p' "$file" | tail -n 1)" fi if [[ -z "$count" ]]; then count="$(grep -Ec '^(hit|found seed)' "$file" || true)" fi echo "${count:-unknown}" } run_once() { local name="$1" shift local stdout_file="$OUT_DIR/$name.stdout" local stderr_file="$OUT_DIR/$name.stderr" local time_file="$OUT_DIR/$name.time" set +e /usr/bin/time -f '%e' -o "$time_file" "$@" >"$stdout_file" 2>"$stderr_file" local status=$? set -e if [[ "$status" -ne 0 ]]; then echo "Command failed: $*" >&2 echo "--- stderr ---" >&2 tail -n 80 "$stderr_file" >&2 || true exit "$status" fi local elapsed elapsed="$(tr -d '\r\n' < "$time_file")" local count count="$(extract_count "$stdout_file")" echo "$elapsed $count" } mean() { awk ' NF { sum += $1; n += 1 } END { if (n == 0) print "nan"; else printf "%.6f", sum / n; } ' } speedup() { local base="$1" local candidate="$2" awk -v base="$base" -v candidate="$candidate" ' BEGIN { if (candidate > 0) printf "%.2fx", base / candidate; else printf "n/a"; } ' } bench_binary() { local label="$1" shift local -a command=("$@") echo echo "== $label ==" echo "Command: ${command[*]}" for ((i = 1; i <= WARMUPS; ++i)); do echo "Warmup $i/$WARMUPS..." run_once "${label// /_}.warmup.$i" "${command[@]}" >/dev/null done local times_file="$OUT_DIR/${label// /_}.times" : > "$times_file" local last_count="unknown" for ((i = 1; i <= RUNS; ++i)); do local result elapsed count result="$(run_once "${label// /_}.run.$i" "${command[@]}")" elapsed="${result%% *}" count="${result#* }" last_count="$count" echo "$elapsed" >> "$times_file" printf 'Run %d/%d: %ss, reported=%s\n' "$i" "$RUNS" "$elapsed" "$count" done local avg avg="$(mean < "$times_file")" printf '%s\t%s\t%s\n' "$label" "$avg" "$last_count" >> "$OUT_DIR/summary.tsv" } print_summary() { echo echo "Summary" printf '%-16s %12s %12s %12s\n' "binary" "avg_s" "reported" "speedup" local user_cpu_avg="" codex_cpu_avg="" user_cuda_avg="" codex_cuda_avg="" while IFS=$'\t' read -r label avg count; do case "$label" in user_cpu) user_cpu_avg="$avg" ;; codex_cpu) codex_cpu_avg="$avg" ;; user_cuda) user_cuda_avg="$avg" ;; codex_cuda) codex_cuda_avg="$avg" ;; esac done < "$OUT_DIR/summary.tsv" while IFS=$'\t' read -r label avg count; do local base="" sp="baseline" case "$label" in codex_cpu) base="$user_cpu_avg" ;; codex_cuda) base="$user_cuda_avg" ;; esac if [[ -n "$base" ]]; then sp="$(speedup "$base" "$avg")" fi printf '%-16s %12s %12s %12s\n' "$label" "$avg" "$count" "$sp" done < "$OUT_DIR/summary.tsv" } echo "Root: $ROOT_DIR" echo "Build dir: $BUILD_DIR" echo "Output dir: $OUT_DIR" echo "Args: ${SEARCH_ARGS[*]}" echo "Runs: $RUNS timed, $WARMUPS warmup" if [[ "$DO_CPU" -eq 1 && "$START_SEED_ARG" != "0" ]]; then echo "Warning: original slime.cpp ignores non-zero -s in its CPU thread split; CPU counts will not be apples-to-apples." >&2 fi : > "$OUT_DIR/summary.tsv" if [[ "$DO_CPU" -eq 1 ]]; then build_cpu bench_binary user_cpu "$BUILD_DIR/user_cpu" "${SEARCH_ARGS[@]}" bench_binary codex_cpu "$BUILD_DIR/codex_cpu" "${SEARCH_ARGS[@]}" --all-locations fi if [[ "$DO_CUDA" -eq 1 ]]; then build_cuda if [[ "$DO_CUDA" -eq 1 ]]; then bench_binary user_cuda "$BUILD_DIR/user_cuda" "${SEARCH_ARGS[@]}" bench_binary codex_cuda "$BUILD_DIR/codex_cuda" "${SEARCH_ARGS[@]}" fi fi print_summary if [[ "$KEEP_OUTPUT" -eq 0 ]]; then find "$OUT_DIR" -type f \( -name '*.stdout' -o -name '*.stderr' -o -name '*.time' -o -name '*.times' \) -delete else echo echo "Kept logs in $OUT_DIR" fi