use std::collections::BTreeMap;
use std::io::{self, Write};
use std::path::PathBuf;
use subms::{SubMsFeatureManifest, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize};
use subms_hyperloglog::HyperLogLog;
const PRECISIONS: [u32; 3] = [12, 15, 18];
const CANON_P: u32 = PRECISIONS[PRECISIONS.len() - 1];
const LIST_LENS: [usize; 3] = [4_096, 32_768, 262_144];
const OPS: usize = 20_000;
const MAX_KEYS: usize = LIST_LENS[LIST_LENS.len() - 1];
const BULK_REPS: usize = 256;
const BULK_WARM: usize = 8;
fn keys() -> Vec<String> {
(0..MAX_KEYS).map(|i| format!("key-{i}")).collect()
}
fn regs(p: u32) -> usize {
1usize << p
}
fn keyed_p50<T>(f: &mut T, ks: &[String], mut op: impl FnMut(&mut T, &str)) -> u64 {
keyed(f, ks, &mut op, true)
}
fn keyed_p99<T>(f: &mut T, ks: &[String], mut op: impl FnMut(&mut T, &str)) -> u64 {
keyed(f, ks, &mut op, false)
}
fn keyed<T>(f: &mut T, ks: &[String], op: &mut impl FnMut(&mut T, &str), median: bool) -> u64 {
let mut h = SubMsPerfHarness::new("hll-feature", "rust");
let st = h.stage("op", ks.len());
for k in ks {
st.time(|| op(f, k));
}
stat(&h, median)
}
fn bulk<T>(mut setup: impl FnMut() -> T, mut op: impl FnMut(&mut T), median: bool) -> u64 {
let mut input = setup();
for _ in 0..BULK_WARM {
op(&mut input);
}
let mut h = SubMsPerfHarness::new("hll-feature", "rust");
let st = h.stage("op", BULK_REPS);
for _ in 0..BULK_REPS {
st.time(|| op(&mut input));
}
stat(&h, median)
}
fn stat(h: &SubMsPerfHarness, median: bool) -> u64 {
summarize(h)
.stages
.iter()
.find(|s| s.name == "op")
.map_or(0, |s| if median { s.p50_ns } else { s.p99_ns })
}
fn sweep(label: &str, mut at: impl FnMut(u32) -> u64) -> Vec<(usize, u64)> {
let rows: Vec<(usize, u64)> = PRECISIONS.iter().map(|&p| (regs(p), at(p))).collect();
eprintln!("sweep {label}: {rows:?}");
rows
}
fn sweep_sizes(
label: &str,
sizes: &[usize],
mut at: impl FnMut(usize) -> u64,
) -> Vec<(usize, u64)> {
let rows: Vec<(usize, u64)> = sizes.iter().map(|&n| (n, at(n))).collect();
eprintln!("sweep {label}: {rows:?}");
rows
}
fn main() -> io::Result<()> {
let ks = keys();
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join(".subms")
.join("features")
.join("rust.json");
let existing = std::fs::read_to_string(&path).unwrap_or_default();
let mut manifest = SubMsFeatureManifest::load_str("rust", &existing);
let (source, instance) = SubMsP99Source::from_env();
manifest.set_p99_source(source, instance.as_deref());
let mut base = HyperLogLog::new(CANON_P);
let base_p50 = keyed_p50(&mut base, &ks[..OPS], |h, k| {
h.add(k);
});
eprintln!("base add p50: {base_p50}ns");
#[cfg(feature = "sparse")]
{
use subms_hyperloglog::SparseHyperLogLog;
let sw = sweep_sizes("sparse/add(list-len)", &LIST_LENS, |n| {
let mut s = SparseHyperLogLog::with_threshold(CANON_P, n + 1);
for k in &ks[..n] {
s.add(k);
}
let mut h = SubMsPerfHarness::new("hll-feature", "rust");
let st = h.stage("op", OPS);
for i in 0..OPS {
let k = &ks[(i * 7919) % n];
st.time(|| s.add(k));
}
stat(&h, true)
});
let (cat, reason) = classify_feature(
&sw,
Some(base_p50),
Some(subms::SubMsFeatureCategory::Structural),
);
let mut s = SparseHyperLogLog::new(CANON_P);
let mut p99 = BTreeMap::new();
p99.insert(
"add".to_string(),
keyed_p99(&mut s, &ks[..OPS], |x, k| {
x.add(k);
}),
);
p99.insert(
"estimate".to_string(),
bulk(
|| {
let mut x = SparseHyperLogLog::new(CANON_P);
for k in &ks[..OPS] {
x.add(k);
}
x
},
|x| _ = x.estimate(),
false,
),
);
manifest.set_feature("sparse", cat, &p99, &reason);
}
#[cfg(feature = "union-intersect")]
{
use subms_hyperloglog::{estimate_intersect, estimate_union};
let build = |p: u32| {
let n = regs(p);
let mut a = HyperLogLog::new(p);
let mut b = HyperLogLog::new(p);
for (i, k) in ks[..n].iter().enumerate() {
a.add(k);
if i % 2 == 0 {
b.add(k);
}
}
(a, b)
};
let sw = sweep("union-intersect/estimate_union", |p| {
bulk(
|| build(p),
|(a, b)| _ = estimate_union(a, b).expect("same precision"),
true,
)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut p99 = BTreeMap::new();
p99.insert(
"union".to_string(),
bulk(
|| build(CANON_P),
|(a, b)| _ = estimate_union(a, b).expect("same precision"),
false,
),
);
p99.insert(
"intersect".to_string(),
bulk(
|| build(CANON_P),
|(a, b)| _ = estimate_intersect(a, b).expect("same precision"),
false,
),
);
manifest.set_feature("union-intersect", cat, &p99, &reason);
}
std::fs::create_dir_all(path.parent().unwrap())?;
std::fs::write(&path, manifest.to_json())?;
io::stdout().write_all(manifest.to_json().as_bytes())?;
Ok(())
}