use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; use rangemap::*; use std::{any::type_name, fmt::Debug, ops::*}; use test_strategy::Arbitrary; type Key = i64; type Value = i64; const COUNT: usize = 100000; const OPERATIONS: usize = 100000; const LOOKUPS: usize = 1000000; #[derive(Debug, Clone, Arbitrary)] enum Operation { Insert(K, V), Delete(K), } fn range_inclusive_map( values: impl Strategy, V)>, size: usize, ) -> impl Strategy> { prop::collection::vec(values, size) .prop_map(|ranges| ranges.into_iter().collect::>()) } fn range_map( values: impl Strategy, V)>, size: usize, ) -> impl Strategy> { prop::collection::vec(values, size) .prop_map(|ranges| ranges.into_iter().collect::>()) } fn criterion_benchmark(c: &mut Criterion) { let mut runner = TestRunner::deterministic(); let mut group = c.benchmark_group(format!( "RangeMap<{}, {}>", type_name::(), type_name::() )); group.throughput(Throughput::Elements(COUNT as u64)); group.bench_function("insert", |b| { let entries = prop::collection::vec(any::<(Range, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); b.iter_with_large_drop(|| { let mut map = RangeMap::new(); for (range, value) in entries.clone().into_iter() { map.insert(range, value); } map }) }); group.throughput(Throughput::Elements(OPERATIONS as u64)); group.bench_function("operations", |b| { let map = range_map(any::<(Range, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); let operations = prop::collection::vec(any::, Value>>(), OPERATIONS) .new_tree(&mut runner) .unwrap() .current(); b.iter_with_large_drop(|| { let mut map = map.clone(); for operation in operations.clone().into_iter() { match operation { Operation::Insert(key, value) => map.insert(key, value), Operation::Delete(key) => map.remove(key), } } map }) }); group.throughput(Throughput::Elements(LOOKUPS as u64)); group.bench_function("lookups", |b| { let map = range_map(any::<(Range, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); let lookups = prop::collection::vec(any::(), LOOKUPS) .new_tree(&mut runner) .unwrap() .current(); b.iter(|| { for lookup in lookups.iter() { black_box(map.get(lookup)); } }) }); group.finish(); let mut group = c.benchmark_group(format!( "RangeInclusiveMap<{}, {}>", type_name::(), type_name::() )); group.throughput(Throughput::Elements(COUNT as u64)); group.bench_function("insert", |b| { let entries = prop::collection::vec(any::<(RangeInclusive, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); b.iter_with_large_drop(|| { let mut map = RangeInclusiveMap::new(); for (range, value) in entries.clone().into_iter() { map.insert(range, value); } map }) }); group.throughput(Throughput::Elements(OPERATIONS as u64)); group.bench_function("operations", |b| { let map = range_inclusive_map(any::<(RangeInclusive, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); let operations = prop::collection::vec(any::, Value>>(), OPERATIONS) .new_tree(&mut runner) .unwrap() .current(); b.iter_with_large_drop(|| { let mut map = map.clone(); for operation in operations.clone().into_iter() { match operation { Operation::Insert(key, value) => map.insert(key, value), Operation::Delete(key) => map.remove(key), } } map }) }); group.throughput(Throughput::Elements(LOOKUPS as u64)); group.bench_function("lookups", |b| { let map = range_inclusive_map(any::<(RangeInclusive, Value)>(), COUNT) .new_tree(&mut runner) .unwrap() .current(); let lookups = prop::collection::vec(any::(), LOOKUPS) .new_tree(&mut runner) .unwrap() .current(); b.iter(|| { for lookup in lookups.iter() { black_box(map.get(lookup)); } }) }); group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches);