diff --git a/Cargo.lock b/Cargo.lock index ec963893ed5..c64685a4244 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,7 @@ dependencies = [ "boa_runtime", "criterion", "jemallocator", + "regex", "walkdir", ] diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 4be3c394e14..18a9b486783 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -11,6 +11,7 @@ boa_runtime.workspace = true [dev-dependencies] criterion.workspace = true walkdir = "2" +regex.workspace = true [target.x86_64-unknown-linux-gnu.dev-dependencies] jemallocator.workspace = true diff --git a/benches/benches/scripts.rs b/benches/benches/scripts.rs index b185966773c..111579e3408 100644 --- a/benches/benches/scripts.rs +++ b/benches/benches/scripts.rs @@ -1,6 +1,7 @@ #![allow(unused_crate_dependencies, missing_docs)] use boa_engine::{ - Context, JsValue, Source, js_string, optimizer::OptimizerOptions, script::Script, + Context, JsValue, Source, js_string, object::JsObject, optimizer::OptimizerOptions, + script::Script, }; use criterion::{Criterion, criterion_group, criterion_main}; use std::{path::Path, time::Duration}; @@ -9,7 +10,45 @@ use std::{path::Path, time::Duration}; #[global_allocator] static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; +struct PreparedScriptBench { + context: Context, + function: JsObject, +} + +fn prepare_script_bench(path: &Path) -> PreparedScriptBench { + let code = std::fs::read_to_string(path).unwrap(); + + let mut context = Context::default(); + context.set_optimizer_options(OptimizerOptions::empty()); + + boa_runtime::register( + boa_runtime::extensions::ConsoleExtension(boa_runtime::NullLogger), + None, + &mut context, + ) + .expect("Runtime registration failed"); + + let script = Script::parse(Source::from_bytes(&code), None, &mut context).unwrap(); + script.codeblock(&mut context).unwrap(); + script.evaluate(&mut context).unwrap(); + + let function = context + .global_object() + .get(js_string!("main"), &mut context) + .unwrap_or_else(|_| panic!("No main function defined in script: {}", path.display())) + .as_callable() + .unwrap_or_else(|| panic!("'main' is not a function in script: {}", path.display())) + .clone(); + + PreparedScriptBench { context, function } +} + fn bench_scripts(c: &mut Criterion) { + let args: Vec = std::env::args().collect(); + let is_list = args.iter().any(|arg| arg == "--list"); + let filter = args.iter().skip(1).find(|arg| !arg.starts_with('-')); + let filter_re = filter.and_then(|f| regex::Regex::new(f).ok()); + let scripts_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts"); let scripts: Vec<_> = walkdir::WalkDir::new(&scripts_dir) @@ -26,50 +65,35 @@ fn bench_scripts(c: &mut Criterion) { for entry in scripts { let path = entry.path(); - let code = std::fs::read_to_string(path).unwrap(); - // Create a nice benchmark name from the relative path let rel_path = path.strip_prefix(&scripts_dir).unwrap().with_extension(""); let name = rel_path.display().to_string(); let mut group = c.benchmark_group(&name); - // Use reduced sample size for slow benchmarks (e.g., v8-benches) if rel_path.starts_with("v8-benches") { group.sample_size(10); group.measurement_time(Duration::from_secs(5)); } - let context = &mut Context::default(); - - // Disable optimizations - context.set_optimizer_options(OptimizerOptions::empty()); - - // Register runtime for console.log support - boa_runtime::register( - boa_runtime::extensions::ConsoleExtension(boa_runtime::NullLogger), - None, - context, - ) - .expect("Runtime registration failed"); - - // Parse and compile once, outside the benchmark loop - let script = Script::parse(Source::from_bytes(&code), None, context).unwrap(); - script.codeblock(context).unwrap(); - - // Evaluate once to define the main function - script.evaluate(context).unwrap(); - - // Get the main function - let function = context - .global_object() - .get(js_string!("main"), context) - .unwrap_or_else(|_| panic!("No main function defined in script: {}", path.display())) - .as_callable() - .unwrap_or_else(|| panic!("'main' is not a function in script: {}", path.display())) - .clone(); - - group.bench_function("Execution", |b| { - b.iter(|| function.call(&JsValue::undefined(), &[], context)); + let should_run = !is_list + && filter.is_none_or(|f| { + let full_name = format!("{name}/Execution"); + if let Some(re) = &filter_re { + re.is_match(&full_name) + } else { + full_name.contains(f) + } + }); + + let mut prepared = should_run.then(|| prepare_script_bench(path)); + + group.bench_function("Execution", move |b| { + if let Some(prepared) = prepared.as_mut() { + let function = prepared.function.clone(); + let context = &mut prepared.context; + + b.iter(|| function.call(&JsValue::undefined(), &[], context)); + } }); group.finish(); }