shnewto / bnf

Parse BNF grammar definitions
MIT License
256 stars 22 forks source link

Divan benchmarking PoC #140

Closed CrockAgile closed 8 months ago

CrockAgile commented 9 months ago

Closes #138

I ported the existing benchmarks to Divan as described in #138.

cargo bench -q --bench divan

Timer precision: 41 ns
divan                                  fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ examples                                          │               │               │               │         │
   ├─ generate_dna                     415 ns        │ 75.45 µs      │ 707 ns        │ 765.5 ns      │ 570652  │ 570652
   ├─ parse_infinite_nullable_grammar  999 ns        │ 2.995 ms      │ 106.4 µs      │ 146.1 µs      │ 34183   │ 34183
   │                                   1 Mitem/s     │ 31.04 Kitem/s │ 469.6 Kitem/s │ 335.2 Kitem/s │         │
   ├─ parse_polish_calculator          4.79 µs       │ 76.19 ms      │ 11.45 µs      │ 852.3 µs      │ 5867    │ 5867
   ╰─ parse_postal                     12.91 µs      │ 1.481 ms      │ 13.29 µs      │ 13.49 µs      │ 369045  │ 369045

TODO

coveralls commented 9 months ago

Coverage Status

coverage: 96.67%. remained the same when pulling 8a77974bb3a4b480b17f71b3cd9a3125bcfa33d2 on divan-compare into 67a7054a13b70a8fc765d851eff056a7eed05dcb on main.

nvzqz commented 9 months ago

Hi, I noticed this PR and wanted to suggest a few improvements if I may 😄


parse_postal can instead be written as:

#[divan::bench]
fn parse_postal(bencher: divan::Bencher) {
    let input = divan::black_box(
        include_str!("../tests/fixtures/postal_address.terminated.input.bnf")
    );

    bencher.bench(|| {
        input.parse::<bnf::Grammar>().unwrap();
    });
}

This ensures each iteration gets an opaque input without the cost of storing duplicate inputs (Bencher::with_inputs allocates storage for all inputs in a sample).


Similarly, parse_infinite_nullable_grammar could be written as:

#[divan::bench]
fn parse_infinite_nullable_grammar(bencher: divan::Bencher) {
    let infinite_grammar: bnf::Grammar = "
            <a> ::= '' | <b>
            <b> ::= <a>"
        .parse()
        .unwrap();

    let parse_count: usize = {
        use rand::Rng;
        let mut rng: rand::rngs::StdRng = rand::SeedableRng::seed_from_u64(0);
        rng.gen_range(1..100);
    };

    bencher
        .counter(parse_count)
        .bench(|| {
            let _: Vec<_> = infinite_grammar.parse_input("").take(parse_count).collect();
        });
}

However, you probably meant to have each iteration use a random parse_count, in which case it could be rewritten to:

#[divan::bench]
fn parse_infinite_nullable_grammar(bencher: divan::Bencher) {
    use rand::Rng;

    let infinite_grammar: bnf::Grammar = "
            <a> ::= '' | <b>
            <b> ::= <a>"
        .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::<divan::counter::ItemsCount>()
        .bench_local_values(|parse_count| {
            let _: Vec<_> = infinite_grammar.parse_input("").take(parse_count).collect();
        });
}

Note that Bencher::count_inputs_as is in 0.1.4.

CrockAgile commented 9 months ago

@nvzqz wow thank you for the great notes! I applied your suggestions. thank you!