From 733a00030582375c43fa156e978f25df6ecc9e9a Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Sun, 18 Apr 2021 14:51:48 +0200 Subject: [PATCH] tooling(bench_util): benching and profiling utilities (#10223) --- Cargo.lock | 13 +++++- Cargo.toml | 1 + bench_util/Cargo.toml | 16 +++++++ bench_util/src/js_runtime.rs | 89 ++++++++++++++++++++++++++++++++++++ bench_util/src/lib.rs | 6 +++ bench_util/src/profiling.rs | 81 ++++++++++++++++++++++++++++++++ core/Cargo.toml | 2 +- core/benches/op_baseline.rs | 77 ++++++------------------------- 8 files changed, 220 insertions(+), 65 deletions(-) create mode 100644 bench_util/Cargo.toml create mode 100644 bench_util/src/js_runtime.rs create mode 100644 bench_util/src/lib.rs create mode 100644 bench_util/src/profiling.rs diff --git a/Cargo.lock b/Cargo.lock index f53edf434d..d694346525 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "Inflector" version = "0.11.4" @@ -194,6 +196,15 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bench_util" +version = "0.0.0" +dependencies = [ + "bencher", + "deno_core", + "tokio", +] + [[package]] name = "bencher" version = "0.1.5" @@ -580,7 +591,7 @@ name = "deno_core" version = "0.84.0" dependencies = [ "anyhow", - "bencher", + "bench_util", "futures", "indexmap", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index b5ffb58f5e..8b1938c29b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "runtime", "serde_v8", "test_plugin", + "bench_util", "test_util", "op_crates/crypto", "op_crates/fetch", diff --git a/bench_util/Cargo.toml b/bench_util/Cargo.toml new file mode 100644 index 0000000000..834d0be539 --- /dev/null +++ b/bench_util/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bench_util" +version = "0.0.0" +authors = ["the Deno authors"] +edition = "2018" +description = "Bench and profiling utilities for deno crates" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bencher = "0.1" +deno_core = { version = "0.84.0", path = "../core" } +tokio = { version = "1.4.0", features = ["full"] } diff --git a/bench_util/src/js_runtime.rs b/bench_util/src/js_runtime.rs new file mode 100644 index 0000000000..701849b5a8 --- /dev/null +++ b/bench_util/src/js_runtime.rs @@ -0,0 +1,89 @@ +use bencher::Bencher; +use deno_core::v8; +use deno_core::JsRuntime; + +use crate::profiling::is_profiling; + +pub fn create_js_runtime(setup: impl FnOnce(&mut JsRuntime)) -> JsRuntime { + let mut rt = JsRuntime::new(Default::default()); + + // Setup bootstrap namespace + rt.execute("bootstrap", "globalThis.__bootstrap = {};") + .unwrap(); + + // Caller provided setup + setup(&mut rt); + + // Init ops + rt.execute( + "init", + r#" + Deno.core.ops(); + Deno.core.registerErrorClass('Error', Error); + "#, + ) + .unwrap(); + + rt +} + +fn loop_code(iters: u64, src: &str) -> String { + format!(r#"for(let i=0; i < {}; i++) {{ {} }}"#, iters, src,) +} + +pub fn bench_js_sync( + b: &mut Bencher, + src: &str, + setup: impl FnOnce(&mut JsRuntime), +) { + let mut runtime = create_js_runtime(setup); + let context = runtime.global_context(); + let scope = &mut v8::HandleScope::with_context(runtime.v8_isolate(), context); + + // Increase JS iterations if profiling for nicer flamegraphs + let inner_iters = 1000 * if is_profiling() { 10000 } else { 1 }; + // Looped code + let looped_src = loop_code(inner_iters, src); + + let code = v8::String::new(scope, looped_src.as_ref()).unwrap(); + let script = v8::Script::compile(scope, code, None).unwrap(); + + // Run once if profiling, otherwise regular bench loop + if is_profiling() { + script.run(scope).unwrap(); + } else { + b.iter(|| { + script.run(scope).unwrap(); + }); + } +} + +pub fn bench_js_async( + b: &mut Bencher, + src: &str, + setup: impl FnOnce(&mut JsRuntime), +) { + let mut runtime = create_js_runtime(setup); + let tokio_runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + // Looped code + let looped = loop_code(1000, src); + let src = looped.as_ref(); + + if is_profiling() { + for _ in 0..10000 { + runtime.execute("inner_loop", src).unwrap(); + let future = runtime.run_event_loop(); + tokio_runtime.block_on(future).unwrap(); + } + } else { + b.iter(|| { + runtime.execute("inner_loop", src).unwrap(); + let future = runtime.run_event_loop(); + tokio_runtime.block_on(future).unwrap(); + }); + } +} diff --git a/bench_util/src/lib.rs b/bench_util/src/lib.rs new file mode 100644 index 0000000000..41aaf40043 --- /dev/null +++ b/bench_util/src/lib.rs @@ -0,0 +1,6 @@ +mod js_runtime; +mod profiling; + +pub use bencher; +pub use js_runtime::*; +pub use profiling::*; // Exports bench_or_profile! macro diff --git a/bench_util/src/profiling.rs b/bench_util/src/profiling.rs new file mode 100644 index 0000000000..498c70b9c6 --- /dev/null +++ b/bench_util/src/profiling.rs @@ -0,0 +1,81 @@ +use bencher::{DynBenchFn, StaticBenchFn, TestDescAndFn, TestOpts}; + +pub fn is_profiling() -> bool { + std::env::var("PROFILING").is_ok() +} + +#[macro_export] +// Tweaked and copied from https://github.com/bluss/bencher/blob/master/macros.rs +macro_rules! bench_or_profile { + ($($group_name:path),+) => { + fn main() { + use $crate::bencher::TestOpts; + use $crate::bencher::run_tests_console; + let mut test_opts = TestOpts::default(); + // check to see if we should filter: + if let Some(arg) = ::std::env::args().skip(1).find(|arg| *arg != "--bench") { + test_opts.filter = Some(arg); + } + let mut benches = Vec::new(); + $( + benches.extend($group_name()); + )+ + + if $crate::is_profiling() { + // Run profling + $crate::run_profiles(&test_opts, benches); + } else { + // Run benches + run_tests_console(&test_opts, benches).unwrap(); + } + } + }; + ($($group_name:path,)+) => { + bench_or_profile!($($group_name),+); + }; +} + +pub fn run_profiles(opts: &TestOpts, tests: Vec) { + let tests = filter_tests(opts, tests); + // let decs = tests.iter().map(|t| t.desc.clone()).collect(); + + println!(); + for b in tests { + println!("Profiling {}", b.desc.name); + run_profile(b); + } + println!(); +} + +fn run_profile(test: TestDescAndFn) { + match test.testfn { + DynBenchFn(bencher) => { + bencher::bench::run_once(|harness| bencher.run(harness)); + } + StaticBenchFn(benchfn) => { + bencher::bench::run_once(|harness| benchfn(harness)); + } + }; +} + +// Copied from https://github.com/bluss/bencher/blob/master/lib.rs +fn filter_tests( + opts: &TestOpts, + tests: Vec, +) -> Vec { + let mut filtered = tests; + + // Remove tests that don't match the test filter + filtered = match opts.filter { + None => filtered, + Some(ref filter) => filtered + .into_iter() + .filter(|test| test.desc.name.contains(&filter[..])) + .collect(), + }; + + // Sort the tests alphabetically + filtered.sort_by(|t1, t2| t1.desc.name.cmp(&t2.desc.name)); + + filtered +} diff --git a/core/Cargo.toml b/core/Cargo.toml index 2fbe192542..deaa3e35fc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -34,7 +34,7 @@ path = "examples/http_bench_json_ops.rs" # These dependencies are only used for the 'http_bench_*_ops' examples. [dev-dependencies] tokio = { version = "1.4.0", features = ["full"] } -bencher = "0.1" +bench_util = { version = "0.0.0", path = "../bench_util" } [[bench]] name = "op_baseline" diff --git a/core/benches/op_baseline.rs b/core/benches/op_baseline.rs index 132d92f000..e930b009a9 100644 --- a/core/benches/op_baseline.rs +++ b/core/benches/op_baseline.rs @@ -1,38 +1,25 @@ -use bencher::{benchmark_group, benchmark_main, Bencher}; - use deno_core::error::AnyError; use deno_core::op_async; use deno_core::op_sync; use deno_core::serialize_op_result; -use deno_core::v8; use deno_core::JsRuntime; use deno_core::Op; use deno_core::OpState; use deno_core::ZeroCopyBuf; +use bench_util::bench_or_profile; +use bench_util::bencher::{benchmark_group, Bencher}; +use bench_util::{bench_js_async, bench_js_sync}; + use std::cell::RefCell; use std::rc::Rc; -fn create_js_runtime() -> JsRuntime { - let mut runtime = JsRuntime::new(Default::default()); - runtime.register_op("pi_json", op_sync(|_, _: (), _| Ok(314159))); - runtime.register_op("pi_async", op_async(op_pi_async)); - runtime.register_op("nop", |state, _, _| { +fn setup(rt: &mut JsRuntime) { + rt.register_op("pi_json", op_sync(|_, _: (), _| Ok(314159))); + rt.register_op("pi_async", op_async(op_pi_async)); + rt.register_op("nop", |state, _, _| { Op::Sync(serialize_op_result(Ok(9), state)) }); - - // Init ops - runtime - .execute( - "init", - r#" - Deno.core.ops(); - Deno.core.registerErrorClass('Error', Error); - "#, - ) - .unwrap(); - - runtime } // this is a function since async closures aren't stable @@ -44,57 +31,21 @@ async fn op_pi_async( Ok(314159) } -pub fn bench_runtime_js(b: &mut Bencher, src: &str) { - let mut runtime = create_js_runtime(); - let context = runtime.global_context(); - let scope = &mut v8::HandleScope::with_context(runtime.v8_isolate(), context); - let code = v8::String::new(scope, src).unwrap(); - let script = v8::Script::compile(scope, code, None).unwrap(); - b.iter(|| { - script.run(scope).unwrap(); - }); -} - -pub fn bench_runtime_js_async(b: &mut Bencher, src: &str) { - let mut runtime = create_js_runtime(); - let tokio_runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - - b.iter(|| { - runtime.execute("inner_loop", src).unwrap(); - let future = runtime.run_event_loop(); - tokio_runtime.block_on(future).unwrap(); - }); -} - fn bench_op_pi_json(b: &mut Bencher) { - bench_runtime_js( - b, - r#"for(let i=0; i < 1e3; i++) { - Deno.core.opSync("pi_json", null); - }"#, - ); + bench_js_sync(b, r#"Deno.core.opSync("pi_json");"#, setup); } fn bench_op_nop(b: &mut Bencher) { - bench_runtime_js( + bench_js_sync( b, - r#"for(let i=0; i < 1e3; i++) { - Deno.core.dispatchByName("nop", null, null, null); - }"#, + r#"Deno.core.dispatchByName("nop", null, null, null);"#, + setup, ); } fn bench_op_async(b: &mut Bencher) { - bench_runtime_js_async( - b, - r#"for(let i=0; i < 1e3; i++) { - Deno.core.opAsync("pi_async", null); - }"#, - ); + bench_js_async(b, r#"Deno.core.opAsync("pi_async");"#, setup); } benchmark_group!(benches, bench_op_pi_json, bench_op_nop, bench_op_async); -benchmark_main!(benches); +bench_or_profile!(benches);