use std::time::Instant;
use armdb::skiplist::node::{ConstNode, random_height};
use armdb::skiplist::{InsertResult, SkipList};
use armdb::{Config, ConstTree, DiskLoc};
use tempfile::TempDir;
const PREFILL_LIST: u64 = 8_000_000;
const PREFILL_TREE: u64 = 4_000_000;
const TOTAL_OPS: u64 = 1_000_000;
const THREADS: &[usize] = &[1, 2, 4, 8, 16];
fn key64(i: u64) -> u64 {
let mut z = i.wrapping_add(0x9E3779B97F4A7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
z ^ (z >> 31)
}
fn key_bytes(i: u64) -> [u8; 8] {
key64(i).to_be_bytes()
}
fn cpu_seconds() -> f64 {
#[cfg(target_os = "linux")]
{
let stat = std::fs::read_to_string("/proc/self/stat").unwrap();
let rest = stat.rsplit(')').next().unwrap();
let fields: Vec<&str> = rest.split_whitespace().collect();
let utime: f64 = fields[11].parse().unwrap();
let stime: f64 = fields[12].parse().unwrap();
let ticks = 100.0; (utime + stime) / ticks
}
#[cfg(not(target_os = "linux"))]
{
0.0
}
}
struct PhaseResult {
threads: usize,
wall_s: f64,
mops: f64,
cpu_ratio: f64,
}
fn print_table(title: &str, results: &[PhaseResult]) {
println!("\n=== {title} ===");
println!(
"{:>8} {:>10} {:>10} {:>10} {:>9}",
"threads", "wall s", "Mops/s", "speedup", "CPU x"
);
let base = results[0].mops;
for r in results {
println!(
"{:>8} {:>10.2} {:>10.2} {:>9.2}x {:>9.1}",
r.threads,
r.wall_s,
r.mops,
r.mops / base,
r.cpu_ratio
);
}
}
fn run_phase(threads: usize, total: u64, f: impl Fn(u64, u64) + Sync) -> PhaseResult {
let per_thread = total / threads as u64;
let cpu0 = cpu_seconds();
let t0 = Instant::now();
std::thread::scope(|s| {
for t in 0..threads {
let f = &f;
let start = t as u64 * per_thread;
s.spawn(move || f(start, start + per_thread));
}
});
let wall_s = t0.elapsed().as_secs_f64();
let cpu_s = cpu_seconds() - cpu0;
PhaseResult {
threads,
wall_s,
mops: (per_thread * threads as u64) as f64 / wall_s / 1e6,
cpu_ratio: cpu_s / wall_s,
}
}
type Node = ConstNode<[u8; 8], 8>;
fn phase_a() {
println!("\n[A] raw SkipList: prefill {PREFILL_LIST} keys…");
let list: SkipList<Node> = SkipList::new(false);
let t0 = Instant::now();
{
let guard = list.collector().enter();
for i in 0..PREFILL_LIST {
let node = Node::alloc(
key_bytes(i),
[0u8; 8],
DiskLoc::new(0, 0, 0),
random_height(),
);
match list.insert(node, &guard) {
InsertResult::Inserted => {}
InsertResult::Exists(_) => panic!("duplicate key in prefill"),
}
}
}
println!(" prefill done in {:.1}s", t0.elapsed().as_secs_f64());
let mut next_base = PREFILL_LIST;
let mut results = Vec::new();
for &t in THREADS {
let base = next_base;
next_base += TOTAL_OPS;
let r = run_phase(t, TOTAL_OPS, |start, end| {
let guard = list.collector().enter();
for i in start..end {
let node = Node::alloc(
key_bytes(base + i),
[0u8; 8],
DiskLoc::new(0, 0, 0),
random_height(),
);
match list.insert(node, &guard) {
InsertResult::Inserted => {}
InsertResult::Exists(_) => panic!("duplicate key"),
}
}
});
results.push(r);
}
print_table(
"Phase A: raw SkipList insert (new keys, write_lock path)",
&results,
);
}
fn phase_bc() {
let dir = TempDir::new().unwrap();
let mut config = Config::balanced().build();
config.shard_count = 16; let tree = ConstTree::<[u8; 8], 16>::open(dir.path(), config).unwrap();
println!("\n[B] ConstTree: prefill {PREFILL_TREE} keys…");
let t0 = Instant::now();
for i in 0..PREFILL_TREE {
tree.insert(&key_bytes(i), &[0u8; 16]).unwrap();
}
println!(" prefill done in {:.1}s", t0.elapsed().as_secs_f64());
let mut next_base = PREFILL_TREE;
let mut results = Vec::new();
for &t in THREADS {
let base = next_base;
next_base += TOTAL_OPS;
let r = run_phase(t, TOTAL_OPS, |start, end| {
for i in start..end {
tree.insert(&key_bytes(base + i), &[0u8; 16]).unwrap();
}
});
results.push(r);
}
print_table("Phase B: ConstTree insert (new keys, end-to-end)", &results);
let mut results = Vec::new();
for &t in THREADS {
let r = run_phase(t, TOTAL_OPS, |start, end| {
for i in start..end {
let k = key_bytes(i % PREFILL_TREE);
tree.put(&k, &[1u8; 16]).unwrap();
}
});
results.push(r);
}
print_table(
"Phase C: ConstTree put over existing keys (SeqLock path) — control",
&results,
);
}
fn main() {
println!(
"insert_scaling gate: {} logical cores",
std::thread::available_parallelism().unwrap()
);
phase_a();
phase_bc();
}