use std::sync::Arc;
use std::time::Instant;
use mir_analyzer::db::{
collect_file_definitions, infer_function, parse_file, AnalyzeFileInput, MirDatabase,
};
use mir_analyzer::{AnalysisSession, BatchOptions, PhpVersion};
fn gen_source(n: usize) -> String {
let mut s = String::from("<?php\n");
for i in 0..n {
s.push_str(&format!(
"function f{i}(int $x, string $y): string {{\n $z = $x + 1;\n return $y . (string)$z;\n}}\n",
));
}
s
}
fn fn_names(n: usize) -> Vec<Arc<str>> {
(0..n).map(|i| Arc::from(format!("f{i}"))).collect()
}
#[test]
#[ignore = "perf measurement; run with --release --ignored"]
fn measure_infer_function_timings() {
const N: usize = 40;
let src = gen_source(N);
let names = fn_names(N);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("perf.php");
std::fs::write(&path, &src).unwrap();
let session = AnalysisSession::new(PhpVersion::LATEST);
session.ensure_all_stubs();
let t_full_walk = {
let t = Instant::now();
let _ = session.analyze_paths(std::slice::from_ref(&path), &BatchOptions::new());
t.elapsed()
};
let db = session.snapshot_db();
let path_str: Arc<str> = Arc::from(path.to_string_lossy().as_ref());
let file = db.lookup_source_file(path_str.as_ref()).unwrap();
let _ = parse_file(&db, file);
let _ = collect_file_definitions(&db, file);
let input = AnalyzeFileInput::new(&db, Arc::from("8.4"));
let t_cold = Instant::now();
for name in &names {
let _ = infer_function(&db, file, name.clone(), input);
}
let elapsed_cold = t_cold.elapsed();
let t_warm = Instant::now();
for name in &names {
let _ = infer_function(&db, file, name.clone(), input);
}
let elapsed_warm = t_warm.elapsed();
eprintln!();
eprintln!("=== infer_function performance ===");
eprintln!("functions in fixture: {N}");
eprintln!();
eprintln!(
"full-file walk (old): {:>10?} ({:.1} us / fn)",
t_full_walk,
t_full_walk.as_micros() as f64 / N as f64
);
eprintln!(
"infer_function cold: {:>10?} ({:.1} us / fn)",
elapsed_cold,
elapsed_cold.as_micros() as f64 / N as f64
);
eprintln!(
"infer_function warm: {:>10?} ({:.1} us / fn)",
elapsed_warm,
elapsed_warm.as_micros() as f64 / N as f64
);
eprintln!();
let speedup = elapsed_cold.as_nanos() as f64 / elapsed_warm.as_nanos().max(1) as f64;
eprintln!("warm/cold speedup: {speedup:.1}x");
assert!(
elapsed_warm * 4 < elapsed_cold,
"memoization not effective: warm {:?} not < cold/4 {:?}",
elapsed_warm,
elapsed_cold / 4
);
}