#[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
name
crate
args
consts
types
sample_count
sample_size
threads
counters
min_time
max_time
skip_ext_time
ignore
§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
Function arguments can be provided to benchmark the function over multiple
cases. This is used for comparing across parameters like collection lengths
and enum
variants. If
you are not comparing cases and just need to pass a value into the
benchmark, instead consider passing local values into the Bencher::bench
closure or use Bencher::with_inputs
for many distinct values.
The following example benchmarks converting a Range
to
Vec
over different lengths:
#[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 generating
inputs based on args
values or providing throughput information via
Counter
s:
use divan::Bencher;
#[divan::bench(args = [1, 2, 3])]
fn bench(bencher: Bencher, len: usize) {
let value = new_value(len);
bencher
.counter(len)
.bench(|| {
do_work(value);
});
}
§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:
bool
for available parallelism (true) or no parallelism.usize
for a specific number of threads. 0 means use available parallelism and 1 means no parallelism.IntoIterator
overusize
for 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 Counter
s 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() {
// ...
}