diff --git a/Cargo.toml b/Cargo.toml index 4062647..b774ab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ features = ["derive"] version = "1.0.61" [dev-dependencies] +divan = "0.1.5" insta = { version = "1.26.0", default-features = false } [dev-dependencies.quickcheck] @@ -49,7 +50,11 @@ unstable = [] tracing = ["dep:tracing", "dep:tracing-subscriber", "dep:tracing-flame"] [[bench]] -name = "bnf" +name = "criterion" +harness = false + +[[bench]] +name = "divan" harness = false [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/benches/README.md b/benches/README.md index ee66daa..f969281 100644 --- a/benches/README.md +++ b/benches/README.md @@ -34,7 +34,7 @@ These benchmarks are not run during continuous integration testing. But if a dev #### Flamegraph -> CARGO_PROFILE_BENCH_DEBUG=true cargo flamegraph --bench bnf -- --bench +> CARGO_PROFILE_BENCH_DEBUG=true cargo flamegraph --bench divan -- --bench `sudo` may be required for `dtrace` on macOS diff --git a/benches/bnf.rs b/benches/criterion.rs similarity index 76% rename from benches/bnf.rs rename to benches/criterion.rs index a762368..6543877 100644 --- a/benches/bnf.rs +++ b/benches/criterion.rs @@ -1,32 +1,11 @@ +mod util; + use bnf::Grammar; use criterion::{criterion_group, criterion_main, Criterion}; use rand::seq::SliceRandom; -#[cfg(feature = "tracing")] -fn init_tracing() -> impl Drop { - use tracing_flame::FlameLayer; - use tracing_subscriber::{fmt, prelude::*}; - let filter_layer = tracing_subscriber::EnvFilter::from_default_env(); - let fmt_layer = fmt::Layer::default(); - let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap(); - - tracing_subscriber::registry() - .with(filter_layer) - .with(fmt_layer) - .with(flame_layer) - .init(); - - _guard -} - -#[cfg(not(feature = "tracing"))] -fn init_tracing() {} - fn examples(c: &mut Criterion) { - init_tracing(); - - #[cfg(feature = "tracing")] - let _span = tracing::span!(tracing::Level::DEBUG, "BENCH EXAMPLES").entered(); + let _tracing = util::init_tracing(); c.bench_function("parse postal", |b| { let input = std::include_str!("../tests/fixtures/postal_address.terminated.input.bnf"); diff --git a/benches/divan.rs b/benches/divan.rs new file mode 100644 index 0000000..8323060 --- /dev/null +++ b/benches/divan.rs @@ -0,0 +1,90 @@ +mod util; + +fn main() { + let _tracing = util::init_tracing(); + + #[cfg(feature = "tracing")] + let _span = tracing::span!(tracing::Level::DEBUG, "BENCH EXAMPLES").entered(); + + divan::main(); +} + +mod examples { + #[divan::bench(min_time = 5, max_time = 60)] + fn parse_postal(bencher: divan::Bencher) { + let input = divan::black_box(include_str!( + "../tests/fixtures/postal_address.terminated.input.bnf" + )); + + bencher.bench(|| { + input.parse::().unwrap(); + }); + } + + #[divan::bench(min_time = 5, max_time = 60)] + fn generate_dna(bencher: divan::Bencher) { + bencher + .with_inputs(|| { + let input = " ::= | + ::= 'A' | 'C' | 'G' | 'T'"; + let grammar: bnf::Grammar = input.parse().unwrap(); + grammar + }) + .bench_refs(|grammar| { + grammar.generate().unwrap(); + }); + } + + #[divan::bench(min_time = 5, max_time = 60)] + fn parse_polish_calculator(bencher: divan::Bencher) { + let polish_calc_grammar: bnf::Grammar = " ::= | + ::= '+' | '-' | '*' | '/' + ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' + " + .parse() + .unwrap(); + + // use pseudo random for consistent metrics + use rand::seq::SliceRandom; + let mut rng: rand::rngs::StdRng = rand::SeedableRng::seed_from_u64(0); + let random_walk_count = 100usize; + let mut random_walks: Vec<_> = (0..random_walk_count) + .map(|_| polish_calc_grammar.generate_seeded(&mut rng).unwrap()) + .collect(); + + random_walks.shuffle(&mut rng); + let random_walks = divan::black_box(random_walks); + let mut index = (0..random_walk_count).cycle(); + + bencher.bench_local(|| { + let index = index.next().unwrap(); + let input = &random_walks[index]; + polish_calc_grammar + .parse_input(input) + .for_each(divan::black_box_drop); + }); + } + + #[divan::bench(min_time = 5, max_time = 60)] + fn parse_infinite_nullable_grammar(bencher: divan::Bencher) { + use rand::Rng; + + let infinite_grammar: bnf::Grammar = " + ::= '' | + ::= " + .parse() + .unwrap(); + + let mut rng: rand::rngs::StdRng = rand::SeedableRng::seed_from_u64(0); + + bencher + .with_inputs(|| rng.gen_range(1..100)) + .count_inputs_as::() + .bench_local_values(|parse_count| { + infinite_grammar + .parse_input("") + .take(parse_count) + .for_each(divan::black_box_drop); + }); + } +} diff --git a/benches/util.rs b/benches/util.rs new file mode 100644 index 0000000..ca98a6a --- /dev/null +++ b/benches/util.rs @@ -0,0 +1,19 @@ +#[cfg(feature = "tracing")] +pub fn init_tracing() -> impl Drop { + use tracing_flame::FlameLayer; + use tracing_subscriber::{fmt, prelude::*}; + let filter_layer = tracing_subscriber::EnvFilter::from_default_env(); + let fmt_layer = fmt::Layer::default(); + let (flame_layer, _guard) = FlameLayer::with_file("./tracing.folded").unwrap(); + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .with(flame_layer) + .init(); + + _guard +} + +#[cfg(not(feature = "tracing"))] +pub fn init_tracing() {}