Attribute Macro divan::bench_group

source ·
#[bench_group]
Expand description

Registers a benchmarking group.

Examples

This is used for setting options shared across #[divan::bench] functions in the same module:

#[divan::bench_group(
    sample_count = 100,
    sample_size = 500,
)]
mod math {
    use divan::black_box;

    #[divan::bench]
    fn add() -> i32 {
        black_box(1) + black_box(42)
    }

    #[divan::bench]
    fn div() -> i32 {
        black_box(1) / black_box(42)
    }
}

fn main() {
    // Run `math::add` and `math::div` benchmarks:
    divan::main();
}

Benchmarking options set on parent groups cascade into child groups and their benchmarks:

#[divan::bench_group(
    sample_count = 100,
    sample_size = 500,
)]
mod parent {
    #[divan::bench_group(sample_size = 1)]
    mod child1 {
        #[divan::bench]
        fn bench() {
            // Will be sampled 100 times with 1 iteration per sample.
        }
    }

    #[divan::bench_group(sample_count = 42)]
    mod child2 {
        #[divan::bench]
        fn bench() {
            // Will be sampled 42 times with 500 iterations per sample.
        }
    }

    mod child3 {
        #[divan::bench(sample_count = 1)]
        fn bench() {
            // Will be sampled 1 time with 500 iterations per sample.
        }
    }
}

Applying this attribute multiple times to the same item will cause a compile error:

#[divan::bench_group]
#[divan::bench_group]
mod math {
    // ...
}

Options

name

By default, the benchmark group uses the module’s name. It can be overridden via the name option:

#[divan::bench_group(name = "my_math")]
mod math {
    #[divan::bench(name = "my_add")]
    fn add() -> i32 {
        // Will appear as "crate_name::my_math::my_add".
    }
}

crate

The path to the specific divan crate instance used by this macro’s generated code can be specified via the crate option. This is applicable when using divan via a macro from your own crate.

extern crate divan as sofa;

#[::sofa::bench_group(crate = ::sofa)]
mod math {
    #[::sofa::bench(crate = ::sofa)]
    fn add() -> i32 {
        // ...
    }
}

sample_count

The number of statistical sample recordings can be set to a predetermined u32 value via the sample_count option. This may be overridden at runtime using either the DIVAN_SAMPLE_COUNT environment variable or --sample-count CLI argument.

#[divan::bench_group(sample_count = 1000)]
mod math {
    #[divan::bench]
    fn add() -> i32 {
        // ...
    }
}

If the threads option is enabled, sample count becomes a multiple of the number of threads. This is because each thread operates over the same sample size to ensure there are always N competing threads doing the same amount of work.

sample_size

The number iterations within each statistical sample can be set to a predetermined u32 value via the sample_size option. This may be overridden at runtime using either the DIVAN_SAMPLE_SIZE environment variable or --sample-size CLI argument.

#[divan::bench_group(sample_size = 1000)]
mod math {
    #[divan::bench]
    fn add() -> i32 {
        // ...
    }
}

threads

See #[divan::bench(threads = ...)].

counters

The Counters of each iteration of benchmarked functions in a group can be set via the counters option. The following example emits info for the number of bytes and number of ints processed when benchmarking slice sorting:

use divan::{Bencher, counter::{BytesCount, ItemsCount}};

const INTS: &[i32] = &[
    // ...
];

#[divan::bench_group(counters = [
    BytesCount::of_slice(INTS),
    ItemsCount::new(INTS.len()),
])]
mod sort {
    use super::*;

    #[divan::bench]
    fn default(bencher: Bencher) {
        bencher
            .with_inputs(|| INTS.to_vec())
            .bench_refs(|ints| ints.sort());
    }

    #[divan::bench]
    fn unstable(bencher: Bencher) {
        bencher
            .with_inputs(|| INTS.to_vec())
            .bench_refs(|ints| ints.sort_unstable());
    }
}

For convenience, singular counter allows a single Counter to be set. The following example emits info for the number of bytes processed when benchmarking char-counting and char-collecting:

use divan::counter::BytesCount;

const STR: &str = "...";

#[divan::bench_group(counter = BytesCount::of_str(STR))]
mod chars {
    use super::STR;

    #[divan::bench]
    fn count() -> usize {
        divan::black_box(STR).chars().count()
    }

    #[divan::bench]
    fn collect() -> String {
        divan::black_box(STR).chars().collect()
    }
}

See:

min_time

The minimum time spent benchmarking each function can be set to a predetermined Duration via the min_time option. This may be overridden at runtime using either the DIVAN_MIN_TIME environment variable or --min-time CLI argument.

Unless skip_ext_time is set, this includes time external to benchmarked functions, such as time spent generating inputs and running Drop.

use std::time::Duration;

#[divan::bench_group(min_time = Duration::from_secs(3))]
mod math {
    #[divan::bench]
    fn add() -> i32 {
        // ...
    }
}

For convenience, min_time can also be set with seconds as u64 or f64. Invalid values will cause a panic at runtime.

#[divan::bench_group(min_time = 2)]
mod int_secs {
    // ...
}

#[divan::bench_group(min_time = 1.5)]
mod float_secs {
    // ...
}

max_time

The maximum time spent benchmarking each function can be set to a predetermined Duration via the max_time option. This may be overridden at runtime using either the DIVAN_MAX_TIME environment variable or --max-time CLI argument.

Unless skip_ext_time is set, this includes time external to benchmarked functions, such as time spent generating inputs and running Drop.

If min_time > max_time, then max_time has priority and min_time will not be reached.

use std::time::Duration;

#[divan::bench_group(max_time = Duration::from_secs(5))]
mod math {
    #[divan::bench]
    fn add() -> i32 {
        // ...
    }
}

For convenience, like min_time, max_time can also be set with seconds as u64 or f64. Invalid values will cause a panic at runtime.

#[divan::bench_group(max_time = 8)]
mod int_secs {
    // ...
}

#[divan::bench_group(max_time = 9.5)]
mod float_secs {
    // ...
}

skip_ext_time

By default, min_time and max_time include time external to benchmarked functions, such as time spent generating inputs and running Drop. Enabling the skip_ext_time option will instead make those options only consider time spent within benchmarked functions. This may be overridden at runtime using either the DIVAN_SKIP_EXT_TIME environment variable or --skip-ext-time CLI argument.

In the following example, max_time only considers time spent running measured_function:

#[divan::bench_group(skip_ext_time)]
mod group {
    #[divan::bench(max_time = 5)]
    fn bench(bencher: divan::Bencher) {
        bencher
            .with_inputs(|| generate_input())
            .bench_values(|input| measured_function(input));
    }
}

This option can be set to an explicit bool value to override parent values:

#[divan::bench_group(skip_ext_time = false)]
mod group {
    // ...
}

ignore

Like #[test] and #[divan::bench], #[divan::bench_group] functions can use #[ignore]:

#[divan::bench_group]
#[ignore]
mod math {
    #[divan::bench]
    fn todo() {
        unimplemented!();
    }
}

This option can also instead be set within the #[divan::bench_group] attribute:

#[divan::bench_group(ignore)]
mod math {
    #[divan::bench]
    fn todo() {
        unimplemented!();
    }
}

Like skip_ext_time, this option can be set to an explicit bool value to override parent values:

#[divan::bench_group(ignore = false)]
mod group {
    // ...
}

This can be used to ignore benchmarks based on a runtime condition. The following example benchmark group will be ignored if an environment variable is not set to “true”:

#[divan::bench_group(
    ignore = std::env::var("BENCH_EXPENSIVE").as_deref() != Ok("true")
)]
mod expensive_benches {
    // ...
}