//! Compile-Budget-Benchmark (Instant-Compile-Anforderung, PLAN.md): //! Projekt mit ~50.000 Zeilen < 1 s, einzelnes Modul (~500 Zeilen) < 50 ms //! (Release-Build). Eigener Harness (kein Criterion — keine //! Fremdabhängigkeit nötig); Lauf mit `cargo bench -p tb-vm`. use std::fmt::Write as _; use std::time::Instant; /// Realistisch gemischtes Modul mit `n_blocks` Codeblöcken /// (~10 Zeilen je Block) plus Prozeduren. fn generate_module(n_blocks: usize, seed: usize) -> String { let mut src = String::new(); let _ = writeln!(src, "' Generiertes Benchmark-Modul {seed}"); let _ = writeln!(src, "DIM feld{seed}%(100)"); for i in 0..n_blocks { let v = format!("v{seed}x{i}"); let _ = writeln!(src, "{v}% = {i} MOD 100"); let _ = writeln!(src, "{v}tot# = {v}% * 1.5 + SQR({v}% + 1)"); let _ = writeln!(src, "IF {v}% > 50 THEN"); let _ = writeln!(src, " {v}s$ = \"gross\" + STR$({v}%)"); let _ = writeln!(src, "ELSE"); let _ = writeln!(src, " {v}s$ = LEFT$(\"klein\", 3)"); let _ = writeln!(src, "END IF"); let _ = writeln!(src, "FOR {v}i% = 1 TO 10"); let _ = writeln!(src, " feld{seed}%({v}i% MOD 100) = {v}i%"); let _ = writeln!(src, "NEXT"); } // Prozeduren am Modulende for p in 0..(n_blocks / 20).max(1) { let _ = writeln!(src, "SUB Tu{seed}p{p} (a%, b#)"); let _ = writeln!(src, " b# = a% * 2 + b#"); let _ = writeln!(src, "END SUB"); } src } fn compile_all(sources: &[(String, String)]) -> usize { let units: Vec<_> = sources .iter() .map(|(name, src)| tb_frontend::source::SourceUnit::new(name, &format!("{name}.bas"), src)) .collect(); let project = tb_vm::compile_project("BENCH", &units, &Default::default(), &[]) .expect("Benchmark-Projekt muss kompilieren"); project.procs.iter().map(|p| p.code.len()).sum() } fn incremental(sources: &[(String, String)], budget: f64) { let mut units: Vec<_> = sources .iter() .map(|(n, s)| tb_frontend::source::SourceUnit::new(n, &format!("{n}.bas"), s)) .collect(); let mut compiler = tb_vm::project::ProjectCompiler::default(); compiler .compile("BENCH", &units, &Default::default(), &[]) .unwrap(); let mut times = Vec::new(); for i in 0..7 { // Private Rumpfänderung mit unverändertem öffentlichen Vertrag. units[0].segments[0].text = sources[0] .1 .replace("a% * 2 + b#", &format!("a% * {} + b#", 3 + i)); let start = Instant::now(); let code = compiler .compile("BENCH", &units, &Default::default(), &[]) .unwrap(); std::hint::black_box(&code); times.push(start.elapsed().as_secs_f64()); assert_eq!(compiler.stats.compiled, 1); assert_eq!(compiler.stats.reused, units.len() - 1); } times.sort_by(f64::total_cmp); let median = times[times.len() / 2]; println!(" Cacheänderung: {} Module, Median {:.2} ms (7 Läufe; Invalidierung + Link), {} neu / {} wiederverwendet; Budget {:.0} ms {}",units.len(),median*1000.0,compiler.stats.compiled,compiler.stats.reused,budget*1000.0,if median = (0..20) .map(|i| (format!("MOD{i}"), generate_module(245, i))) .collect(); let project_lines: usize = modules.iter().map(|(_, s)| s.lines().count()).sum(); // Aufwärmen let _ = compile_all(&[("WARM".into(), single.clone())]); let t = Instant::now(); let mut best_single = f64::MAX; for _ in 0..10 { let t1 = Instant::now(); let _ = compile_all(&[("EINZEL".into(), single.clone())]); best_single = best_single.min(t1.elapsed().as_secs_f64()); } let _ = t; let t2 = Instant::now(); let instrs = compile_all(&modules); let project_secs = t2.elapsed().as_secs_f64(); println!("Compile-Budget-Benchmark (Release):"); println!( " Einzelmodul: {single_lines} Zeilen in {:.2} ms (Budget 50 ms) {}", best_single * 1000.0, if best_single < 0.050 { "OK" } else { "VERFEHLT" } ); println!( " Projekt: {project_lines} Zeilen in {:.0} ms (Budget 1000 ms) {} — {instrs} Instruktionen", project_secs * 1000.0, if project_secs < 1.0 { "OK" } else { "VERFEHLT" } ); let lps = project_lines as f64 / project_secs; println!(" Durchsatz: {:.0} Zeilen/s", lps); assert!(best_single < 0.050, "Einzelmodul-Budget verfehlt"); assert!(project_secs < 1.0, "Projekt-Budget verfehlt"); incremental(&[("EINZEL".into(), single)], 0.050); incremental(&modules, 1.0); }