use std::collections::BTreeMap;
use std::io::{self, Write};
use std::path::PathBuf;
use subms::{SubMsFeatureManifest, SubMsP99Source, SubMsPerfHarness, classify_feature, summarize};
use subms_treap::Treap;
const SIZES: [usize; 3] = [4_096, 32_768, 262_144];
const CANON: usize = SIZES[SIZES.len() - 1];
const SEED: u64 = 0;
const OPS: usize = 20_000;
const BULK_REPS: usize = 256;
const BULK_WARM: usize = 8;
const KEY_SPACE: u64 = 1_000_000_007;
fn key_at(i: usize) -> u64 {
((i as u64).wrapping_mul(2_654_435_761)) % 1_000_000_007
}
fn build(n: usize) -> Treap<u64, u64> {
let mut t = Treap::new(SEED);
for i in 0..n {
t.insert(key_at(i), i as u64);
}
t
}
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 keyed(n: usize, mut op: impl FnMut(usize), median: bool) -> u64 {
let mut h = SubMsPerfHarness::new("treap-feature", "rust");
let st = h.stage("op", OPS);
for i in 0..OPS {
let idx = (i * 7919) % n;
st.time(|| op(idx));
}
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("treap-feature", "rust");
let st = h.stage("op", BULK_REPS);
for _ in 0..BULK_REPS {
st.time(|| op(&mut input));
}
stat(&h, median)
}
fn sweep(label: &str, 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 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 base = build(CANON);
let base_p50 = keyed(CANON, |i| _ = base.get(&key_at(i)), true);
eprintln!("base get p50: {base_p50}ns");
#[cfg(feature = "persistent")]
{
use subms_treap::PersistentTreap;
let sw = sweep("persistent/insert", |n| {
let mut p = PersistentTreap::new(SEED);
for i in 0..n {
p = p.insert(key_at(i), i as u64);
}
keyed(n, |i| _ = p.insert(key_at(i), i as u64), true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut p = PersistentTreap::new(SEED);
for i in 0..CANON {
p = p.insert(key_at(i), i as u64);
}
let mut p99 = BTreeMap::new();
p99.insert(
"insert".to_string(),
keyed(CANON, |i| _ = p.insert(key_at(i), i as u64), false),
);
p99.insert(
"get".to_string(),
keyed(CANON, |i| _ = p.get(&key_at(i)), false),
);
p99.insert(
"remove".to_string(),
keyed(CANON, |i| _ = p.remove(&key_at(i)), false),
);
manifest.set_feature("persistent", cat, &p99, &reason);
}
#[cfg(feature = "merge-split")]
{
use subms_treap::SplittableTreap;
let make = |n: usize| {
let mut t = SplittableTreap::new(SEED);
for i in 0..n {
t.insert(key_at(i), i as u64);
}
Some(t)
};
let round_trip = |slot: &mut Option<SplittableTreap<u64, u64>>| {
let t = slot.take().expect("round trip restores the treap");
let (l, r) = t.split(&(KEY_SPACE / 2));
*slot = Some(SplittableTreap::merge(l, r));
};
let sw = sweep("merge-split/split+merge", |n| {
bulk(|| make(n), round_trip, true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let mut p99 = BTreeMap::new();
p99.insert(
"split_merge".to_string(),
bulk(|| make(CANON), round_trip, false),
);
manifest.set_feature("merge-split", cat, &p99, &reason);
}
#[cfg(feature = "concurrent-reads")]
{
use subms_treap::TreapSnapshot;
let sw = sweep("concurrent-reads/snapshot", |n| {
let t = build(n);
bulk(|| (), |()| _ = TreapSnapshot::from_treap(&t), true)
});
let (cat, reason) = classify_feature(&sw, Some(base_p50), None);
let t = build(CANON);
let snap = TreapSnapshot::from_treap(&t);
let mut p99 = BTreeMap::new();
p99.insert(
"snapshot".to_string(),
bulk(|| (), |()| _ = TreapSnapshot::from_treap(&t), false),
);
p99.insert(
"lookup_on_snapshot".to_string(),
keyed(CANON, |i| _ = snap.get(&key_at(i)), false),
);
manifest.set_feature("concurrent-reads", 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(())
}