use commonware_codec::EncodeSize;
use commonware_utils::bitmap::roaring::Bitmap;
use roaring::RoaringTreemap;
fn near_saturated(total: u64, num_gaps: u64) -> (Bitmap, RoaringTreemap) {
let mut ours = Bitmap::new();
let mut theirs = RoaringTreemap::new();
let stride = if num_gaps == 0 {
u64::MAX
} else {
total / num_gaps
};
for i in 0..total {
if num_gaps > 0 && i > 0 && i % stride == 0 {
continue;
}
ours.insert(i);
theirs.insert(i);
}
(ours, theirs)
}
fn sparse_single_shelf(count: u64) -> (Bitmap, RoaringTreemap) {
let mut ours = Bitmap::new();
let mut theirs = RoaringTreemap::new();
let stride = 65_536 / count.max(1);
for i in 0..count {
let v = i * stride;
ours.insert(v);
theirs.insert(v);
}
(ours, theirs)
}
fn multi_shelf(shelves: u64, count_per_shelf: u64) -> (Bitmap, RoaringTreemap) {
let mut ours = Bitmap::new();
let mut theirs = RoaringTreemap::new();
for s in 0..shelves {
let base = s * 65_536;
for i in 0..count_per_shelf {
let v = base + i;
ours.insert(v);
theirs.insert(v);
}
}
(ours, theirs)
}
fn measure(name: &str, ours: &Bitmap, theirs: &RoaringTreemap) {
assert_eq!(
ours.len(),
theirs.len(),
"cardinality mismatch in workload {name}: ours={}, theirs={}",
ours.len(),
theirs.len()
);
let (a, b, r) = ours.container_variant_counts();
let containers = ours.container_count();
let ours_wire = ours.encode_size();
let theirs_wire = theirs.serialized_size();
let abr = format!("A={a} B={b} R={r}");
println!(
"| {:38} | {:>11} | {:>10} | {:>14} | {:>10} | {:>11} |",
name,
ours.len(),
containers,
abr,
ours_wire,
theirs_wire
);
}
fn print_header() {
println!(
"| {:38} | {:>11} | {:>10} | {:>14} | {:>10} | {:>11} |",
"workload", "cardinality", "containers", "A/B/R (ours)", "ours_wire", "theirs_wire"
);
println!(
"|{:-<40}|{:->13}|{:->12}|{:->16}|{:->12}|{:->13}|",
"", ":", ":", "", ":", ":"
);
}
fn main() {
println!("# Roaring memory profile: commonware vs roaring-rs");
println!();
println!("Generated by `cargo bench -p commonware-utils --bench roaring_memory --features analysis`.");
println!();
print_header();
let scenarios: &[(&str, u64)] = &[
("near-saturated: 65k, no gaps", 0),
("near-saturated: 65k, 50 gaps", 50),
("near-saturated: 65k, 500 gaps", 500),
("near-saturated: 65k, 5000 gaps", 5000),
];
for (name, gaps) in scenarios {
let (ours, theirs) = near_saturated(65_536, *gaps);
measure(name, &ours, &theirs);
}
for &count in &[100u64, 1000, 4000] {
let (ours, theirs) = sparse_single_shelf(count);
let name = format!("sparse single shelf: {count} values");
measure(&name, &ours, &theirs);
}
for &shelves in &[10u64, 100, 1000] {
let count_per_shelf = 100u64;
let (ours, theirs) = multi_shelf(shelves, count_per_shelf);
let name = format!("multi-shelf: {shelves} shelves x {count_per_shelf}");
measure(&name, &ours, &theirs);
}
println!();
println!("**Notes**");
println!();
println!("- `ours_wire` and `theirs_wire` use *different* serialization formats and are");
println!(" not byte-comparable, but both reflect the underlying density and so are");
println!(" proxies for relative encoded size.");
println!("- `A/B/R` is the count of Array/Bitmap/Run container variants in the result.");
}