Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112//! Inner-loop evaluation benchmark: deserialize the cached dumps in `bench/fixtures/`//! once, then repeatedly run a stand-in "candidate" against each and score it with//! `harness::compare::compare()`. No engine exists yet (`roadmap.md` -> "Phase 0"), so//! the candidate is the identity function on a cloned dump; what this measures is the//! harness-side cost every real candidate will also pay -- deserialize once, clone or//! reconstruct a `Dump`-shaped result per case, and compare it -- not candidate//! semantics.//!//! `run-inner-loop.sh` runs this across thread counts 1, 2, 4, 8, 16 to build the scaling//! curve. Standalone: `cargo run --release --bin inner_loop -- --threads 4 --seconds 2`.
use std::path::PathBuf;use std::sync::atomic::{AtomicU64, Ordering};use std::sync::Arc;use std::time::{Duration, Instant};
use anyhow::{bail, Context, Result};use harness::compare::compare;use harness::dump::Dump;
struct Args { threads: usize, seconds: f64,}
fn parse_args() -> Result<Args> { let mut threads = 1usize; let mut seconds = 2.0f64; let mut it = std::env::args().skip(1); while let Some(arg) = it.next() { match arg.as_str() { "--threads" => threads = it.next().unwrap_or_default().parse()?, "--seconds" => seconds = it.next().unwrap_or_default().parse()?, other => bail!("unrecognized argument: {other}"), } } Ok(Args { threads, seconds })}
/// A cheap per-case perturbation so `compare` walks both the passing and failing branch/// of every measurement, rather than only ever comparing a dump against an identical/// clone.fn perturb(dump: &Dump, i: usize) -> Dump { let mut out = dump.clone(); let len = out.nodes.len().max(1); if let Some(node) = out.nodes.get_mut(i % len) { node.bounding.x += if i % 7 == 0 { 0.5 } else { 0.0 }; } out}
fn load_fixtures() -> Result<Vec<Dump>> { let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures"); let mut dumps = Vec::new(); for entry in std::fs::read_dir(&dir) .with_context(|| format!("reading fixtures from {}", dir.display()))? { let entry = entry?; if entry.path().extension().and_then(|e| e.to_str()) != Some("json") { continue; } let text = std::fs::read_to_string(entry.path())?; dumps.push(serde_json::from_str(&text)?); } if dumps.is_empty() { bail!("no fixtures in {}; run `cargo run --release --bin gen_fixtures` first", dir.display()); } Ok(dumps)}
fn main() -> Result<()> { let args = parse_args()?; let dumps = Arc::new(load_fixtures()?); println!("loaded {} fixture(s)", dumps.len());
let deadline = Instant::now() + Duration::from_secs_f64(args.seconds); let counter = Arc::new(AtomicU64::new(0)); let mut handles = Vec::new(); for t in 0..args.threads { let dumps = Arc::clone(&dumps); let counter = Arc::clone(&counter); handles.push(std::thread::spawn(move || { let mut i = t; while Instant::now() < deadline { let dump = &dumps[i % dumps.len()]; let candidate = perturb(dump, i); let outcome = compare(dump, &candidate); std::hint::black_box(outcome); counter.fetch_add(1, Ordering::Relaxed); i += args.threads.max(1); } })); } let start = Instant::now(); for h in handles { h.join().expect("worker thread panicked"); } let elapsed = start.elapsed().as_secs_f64(); let total = counter.load(Ordering::Relaxed); let per_sec = total as f64 / elapsed;
let result = serde_json::json!({ "threads": args.threads, "cases": total, "elapsed_s": elapsed, "cases_per_s": per_sec, "cases_per_s_per_core": per_sec / args.threads as f64, }); println!("{result}"); Ok(())}