#[bench]Expand description
Registers a benchmarking function.
§Examples
The quickest way to get started is to benchmark the function as-is:
use divan::black_box;
#[divan::bench]
fn add() -> i32 {
black_box(1) + black_box(42)
}
fn main() {
// Run `add` benchmark:
divan::main();
}If benchmarks need to setup context before running, they can take a
Bencher and use Bencher::bench:
use divan::{Bencher, black_box};
#[divan::bench]
fn copy_from_slice(bencher: Bencher) {
let src = (0..100).collect::<Vec<i32>>();
let mut dst = vec![0; src.len()];
bencher.bench_local(move || {
black_box(&mut dst).copy_from_slice(black_box(&src));
});
}Applying this attribute multiple times to the same item will cause a compile error:
#[divan::bench]
#[divan::bench]
fn bench() {
// ...
}§Drop
When a benchmarked function returns a value, it will not be dropped until after the current sample loop is finished. This allows for more precise timing measurements.
Note that there is an inherent memory cost to defer drop, including allocations inside not-yet-dropped values. Also, if the benchmark panics, the values will never be dropped.
The following example benchmarks will only measure String construction
time, but not deallocation time:
use divan::{Bencher, black_box};
#[divan::bench]
fn freestanding() -> String {
black_box("hello").to_uppercase()
}
#[divan::bench]
fn contextual(bencher: Bencher) {
// Setup:
let s: String = // ...
bencher.bench(|| -> String {
black_box(&s).to_lowercase()
});
}If the returned value does not need to be dropped, there is no memory cost. Because of this, the following example benchmarks are equivalent:
#[divan::bench]
fn with_return() -> i32 {
let n: i32 = // ...
n
}
#[divan::bench]
fn without_return() {
let n: i32 = // ...
divan::black_box(n);
}§Options
namecrateargsconststypessample_countsample_sizethreadscountersmin_timemax_timeskip_ext_timeignore
§name
By default, the benchmark uses the function’s name. It can be overridden via
the name option:
#[divan::bench(name = "my_add")]
fn add() -> i32 {
// Will appear as "crate_name::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(crate = ::sofa)]
fn add() -> i32 {
// ...
}§args
Divan supports providing runtime arguments to benchmarked functions.
#[divan::bench(args = [1000, LEN, len()])]
fn init_vec(len: usize) -> Vec<usize> {
(0..len).collect()
}
const LEN: usize = // ...
fn len() -> usize {
// ...
}The list of arguments can be shared across multiple benchmarks through an
external Iterator:
const LENS: &[usize] = // ...
#[divan::bench(args = LENS)]
fn bench_vec1(len: usize) -> Vec<usize> {
// ...
}
#[divan::bench(args = LENS)]
fn bench_vec2(len: usize) -> Vec<usize> {
// ...
}Unlike the consts option, any argument type is supported if it
implements Any, Copy, Send, Sync, and ToString (or
Debug):
#[derive(Clone, Copy, Debug)]
enum Arg {
A, B
}
#[divan::bench(args = [Arg::A, Arg::B])]
fn bench_args(arg: Arg) {
// ...
}The argument type does not need to implement Copy if it is used through
a reference:
#[derive(Debug)]
enum Arg {
A, B
}
#[divan::bench(args = [Arg::A, Arg::B])]
fn bench_args(arg: &Arg) {
// ...
}For convenience, common string types are coerced to &str:
fn strings() -> impl Iterator<Item = String> {
// ...
}
#[divan::bench(args = strings())]
fn bench_strings(s: &str) {
// ...
}Arguments can also be used with Bencher. This allows for providing
throughput information via Counters:
use divan::Bencher;
#[divan::bench(args = [1, 2, 3])]
fn bench(bencher: Bencher, len: usize) {
bencher
.counter(len)
.bench(|| {
// ...
});
}§consts
Divan supports benchmarking functions with const
generics
via the consts option.
The following example benchmarks initialization of [i32; N]
for values of N provided by a literal,
const item,
and const fn:
#[divan::bench(consts = [1000, LEN, len()])]
fn init_array<const N: usize>() -> [i32; N] {
let mut result = [0; N];
for i in 0..N {
result[i] = divan::black_box(i as i32);
}
result
}
const LEN: usize = // ...
const fn len() -> usize {
// ...
}The list of constants can be shared across multiple benchmarks through an external array or slice:
const SIZES: &[usize] = &[1, 2, 5, 10];
#[divan::bench(consts = SIZES)]
fn bench_array1<const N: usize>() -> [i32; N] {
// ...
}
#[divan::bench(consts = SIZES)]
fn bench_array2<const N: usize>() -> [i32; N] {
// ...
}External constants are limited to lengths 1 through 20, because of implementation details. This limit does not apply if the list is provided directly like in the first example.
const SIZES: [usize; 21] = [
// ...
];
#[divan::bench(consts = SIZES)]
fn bench_array<const N: usize>() -> [i32; N] {
// ...
}§types
Divan supports benchmarking generic functions over a list of types via the
types option.
The following example benchmarks the From<&str> implementations
for &str and String:
#[divan::bench(types = [&str, String])]
fn from_str<'a, T>() -> T
where
T: From<&'a str>,
{
divan::black_box("hello world").into()
}The types and args options can be combined to benchmark T × A
scenarios. The following example benchmarks the FromIterator
implementations for Vec, BTreeSet, and HashSet:
use std::collections::{BTreeSet, HashSet};
#[divan::bench(
types = [Vec<i32>, BTreeSet<i32>, HashSet<i32>],
args = [0, 2, 4, 16, 256, 4096],
)]
fn from_range<T>(n: i32) -> T
where
T: FromIterator<i32>,
{
(0..n).collect()
}§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(sample_count = 1000)]
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 statistics 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(sample_size = 1000)]
fn add() -> i32 {
// ...
}§threads
Benchmarked functions can be run across multiple threads via the threads
option. This enables you to measure contention on atomics and
locks. The default thread count is the available parallelism.
use std::sync::Arc;
#[divan::bench(threads)]
fn arc_clone(bencher: divan::Bencher) {
let arc = Arc::new(42);
bencher.bench(|| arc.clone());
}The threads option can be set to any of:
boolfor available parallelism (true) or no parallelism.usizefor a specific number of threads. 0 means use available parallelism and 1 means no parallelism.IntoIteratoroverusizefor multiple thread counts, such as:
#[divan::bench(threads = false)]
fn single() {
// ...
}
#[divan::bench(threads = 10)]
fn specific() {
// ...
}
#[divan::bench(threads = 0..=8)]
fn range() {
// Note: Includes 0 for available parallelism.
}
#[divan::bench(threads = [0, 1, 4, 8, 16])]
fn selection() {
// ...
}§counters
The Counters of each iteration 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(counters = [
BytesCount::of_slice(INTS),
ItemsCount::new(INTS.len()),
])]
fn sort(bencher: Bencher) {
bencher
.with_inputs(|| INTS.to_vec())
.bench_refs(|ints| ints.sort());
}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:
use divan::counter::BytesCount;
const STR: &str = "...";
#[divan::bench(counter = BytesCount::of_str(STR))]
fn char_count() -> usize {
divan::black_box(STR).chars().count()
}See:
§bytes_count
Convenience shorthand for
counter = BytesCount::from(n).
§chars_count
Convenience shorthand for
counter = CharsCount::from(n).
§items_count
Convenience shorthand for
counter = ItemsCount::from(n).
§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 the
benchmarked function, such as time spent generating inputs and running
Drop.
use std::time::Duration;
#[divan::bench(min_time = Duration::from_secs(3))]
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(min_time = 2)]
fn int_secs() -> i32 {
// ...
}
#[divan::bench(min_time = 1.5)]
fn float_secs() -> i32 {
// ...
}§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 the
benchmarked function, 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(max_time = Duration::from_secs(5))]
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(max_time = 8)]
fn int_secs() -> i32 {
// ...
}
#[divan::bench(max_time = 9.5)]
fn float_secs() -> i32 {
// ...
}§skip_ext_time
By default, min_time and max_time include time external to the
benchmarked function, 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 the benchmarked function. 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(max_time = 5, skip_ext_time)]
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(max_time = 5, skip_ext_time = false)]
fn bench(bencher: divan::Bencher) {
// ...
}§ignore
Like #[test],
#[divan::bench] functions can use #[ignore]:
#[divan::bench]
#[ignore]
fn todo() {
unimplemented!();
}This option can also instead be set within the #[divan::bench] attribute:
#[divan::bench(ignore)]
fn todo() {
unimplemented!();
}Like skip_ext_time, this option can be set to an explicit bool value
to override parent values:
#[divan::bench(ignore = false)]
fn bench() {
// ...
}This can be used to ignore benchmarks based on a runtime condition. The following example benchmark will be ignored if an environment variable is not set to “true”:
#[divan::bench(
ignore = std::env::var("BENCH_EXPENSIVE").as_deref() != Ok("true")
)]
fn expensive_bench() {
// ...
}