use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use ftui_render::roaring_bitmap::RoaringBitmap;
use std::hint::black_box;
const SIZES: &[(u32, u32)] = &[(80, 24), (120, 40), (200, 60), (400, 100)];
const SPARSE_FRACTION: f64 = 0.05; const DENSE_FRACTION: f64 = 0.50;
fn dirty_cells(width: u32, height: u32, fraction: f64) -> Vec<u32> {
let total = (width * height) as usize;
let count = ((total as f64) * fraction).ceil() as usize;
let mut indices = Vec::with_capacity(count);
let mut state: u64 = 0xDEAD_BEEF;
while indices.len() < count {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let idx = ((state >> 33) as u32) % (width * height);
indices.push(idx);
}
indices
}
fn bench_construction(c: &mut Criterion) {
for &(w, h) in SIZES {
let total = w * h;
let label = format!("{w}x{h}");
let cells = dirty_cells(w, h, SPARSE_FRACTION);
let mut group = c.benchmark_group(format!("dirty_construct/sparse_{label}"));
group.throughput(Throughput::Elements(cells.len() as u64));
group.bench_function(BenchmarkId::new("roaring", &label), |b| {
b.iter(|| {
let mut bm = RoaringBitmap::new();
for &idx in &cells {
bm.insert(idx);
}
black_box(bm.cardinality())
});
});
group.bench_function(BenchmarkId::new("vec_bool", &label), |b| {
b.iter(|| {
let mut v = vec![false; total as usize];
for &idx in &cells {
v[idx as usize] = true;
}
black_box(v.iter().filter(|&&x| x).count())
});
});
group.finish();
let cells = dirty_cells(w, h, DENSE_FRACTION);
let mut group = c.benchmark_group(format!("dirty_construct/dense_{label}"));
group.throughput(Throughput::Elements(cells.len() as u64));
group.bench_function(BenchmarkId::new("roaring", &label), |b| {
b.iter(|| {
let mut bm = RoaringBitmap::new();
for &idx in &cells {
bm.insert(idx);
}
black_box(bm.cardinality())
});
});
group.bench_function(BenchmarkId::new("vec_bool", &label), |b| {
b.iter(|| {
let mut v = vec![false; total as usize];
for &idx in &cells {
v[idx as usize] = true;
}
black_box(v.iter().filter(|&&x| x).count())
});
});
group.finish();
}
}
fn bench_union(c: &mut Criterion) {
for &(w, h) in SIZES {
let total = w * h;
let label = format!("{w}x{h}");
let cells_a = dirty_cells(w, h, SPARSE_FRACTION);
let cells_b: Vec<u32> = {
let mut state: u64 = 0xCAFE_BABE;
let count = cells_a.len();
let mut v = Vec::with_capacity(count);
while v.len() < count {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let idx = ((state >> 33) as u32) % (w * h);
v.push(idx);
}
v
};
let mut group = c.benchmark_group(format!("dirty_union/sparse_{label}"));
group.throughput(Throughput::Elements((cells_a.len() + cells_b.len()) as u64));
group.bench_function(BenchmarkId::new("roaring", &label), |b| {
let mut bm_a = RoaringBitmap::new();
for &idx in &cells_a {
bm_a.insert(idx);
}
let mut bm_b = RoaringBitmap::new();
for &idx in &cells_b {
bm_b.insert(idx);
}
b.iter(|| {
let result = bm_a.union(&bm_b);
black_box(result.cardinality())
});
});
group.bench_function(BenchmarkId::new("vec_bool", &label), |b| {
let mut va = vec![false; total as usize];
for &idx in &cells_a {
va[idx as usize] = true;
}
let mut vb = vec![false; total as usize];
for &idx in &cells_b {
vb[idx as usize] = true;
}
b.iter(|| {
let result: Vec<bool> = va.iter().zip(vb.iter()).map(|(&a, &b)| a | b).collect();
black_box(result.iter().filter(|&&x| x).count())
});
});
group.finish();
}
}
fn bench_iteration(c: &mut Criterion) {
for &(w, h) in SIZES {
let total = w * h;
let label = format!("{w}x{h}");
for &(frac, frac_name) in &[(SPARSE_FRACTION, "sparse"), (DENSE_FRACTION, "dense")] {
let cells = dirty_cells(w, h, frac);
let mut bm = RoaringBitmap::new();
for &idx in &cells {
bm.insert(idx);
}
let mut v = vec![false; total as usize];
for &idx in &cells {
v[idx as usize] = true;
}
let mut group = c.benchmark_group(format!("dirty_iter/{frac_name}_{label}"));
group.throughput(Throughput::Elements(bm.cardinality() as u64));
group.bench_function(BenchmarkId::new("roaring", &label), |b| {
b.iter(|| {
let mut sum: u64 = 0;
for idx in bm.iter() {
sum = sum.wrapping_add(idx as u64);
}
black_box(sum)
});
});
group.bench_function(BenchmarkId::new("vec_bool", &label), |b| {
b.iter(|| {
let mut sum: u64 = 0;
for (i, &dirty) in v.iter().enumerate() {
if dirty {
sum = sum.wrapping_add(i as u64);
}
}
black_box(sum)
});
});
group.finish();
}
}
}
fn bench_memory(c: &mut Criterion) {
let mut group = c.benchmark_group("dirty_memory");
for &(w, h) in SIZES {
let total = w * h;
let label = format!("{w}x{h}");
for &(frac, frac_name) in &[(SPARSE_FRACTION, "sparse"), (DENSE_FRACTION, "dense")] {
let cells = dirty_cells(w, h, frac);
group.bench_function(
BenchmarkId::new(format!("roaring/{frac_name}"), &label),
|b| {
b.iter(|| {
let mut bm = RoaringBitmap::new();
for &idx in &cells {
bm.insert(idx);
}
black_box(bm.cardinality())
});
},
);
group.bench_function(
BenchmarkId::new(format!("vec_bool/{frac_name}"), &label),
|b| {
b.iter(|| {
let mut v = vec![false; total as usize];
for &idx in &cells {
v[idx as usize] = true;
}
black_box(v.len())
});
},
);
}
}
group.finish();
}
criterion_group!(
benches,
bench_construction,
bench_union,
bench_iteration,
bench_memory,
);
criterion_main!(benches);