#![allow(
clippy::cast_precision_loss,
reason = "benchmark throughput math needs usize -> f64; precision loss is cosmetic"
)]
use hyperdb_api::{Connection, CreateMode, HyperProcess, Result};
use std::env;
use std::time::Instant;
const DEFAULT_KEYS: usize = 50_000;
const BATCH: usize = 25;
fn key_count() -> usize {
env::args()
.nth(1)
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(DEFAULT_KEYS)
}
fn throughput(label: &str, keys: usize, secs: f64) {
let per_sec = if secs > 0.0 { keys as f64 / secs } else { 0.0 };
println!(" {label:<28} {keys} keys in {secs:>7.3}s => {per_sec:>12.0} keys/sec");
}
fn bench_single(conn: &Connection, keys: usize) -> Result<f64> {
let kv = conn.kv_store("bench_single")?;
kv.clear()?;
let start = Instant::now();
for i in 0..keys {
kv.set(&format!("k{i}"), "value")?;
}
Ok(start.elapsed().as_secs_f64())
}
fn bench_overwrite(conn: &Connection, keys: usize) -> Result<f64> {
let kv = conn.kv_store("bench_overwrite")?;
kv.clear()?;
for i in 0..keys {
kv.set(&format!("k{i}"), "seed")?;
}
let start = Instant::now();
for i in 0..keys {
kv.set(&format!("k{i}"), "value")?;
}
Ok(start.elapsed().as_secs_f64())
}
fn bench_batched(conn: &Connection, keys: usize) -> Result<f64> {
let kv = conn.kv_store("bench_batched")?;
kv.clear()?;
let start = Instant::now();
let mut i = 0;
while i < keys {
let end = (i + BATCH).min(keys);
let owned: Vec<(String, String)> = (i..end)
.map(|n| (format!("k{n}"), "value".to_string()))
.collect();
let batch: Vec<(&str, &str)> = owned
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
kv.set_batch(&batch)?;
i = end;
}
Ok(start.elapsed().as_secs_f64())
}
fn seed(conn: &Connection, store: &str, keys: usize) -> Result<()> {
let kv = conn.kv_store(store)?;
kv.clear()?;
for i in 0..keys {
kv.set(&format!("k{i}"), "value")?;
}
Ok(())
}
fn bench_get(conn: &Connection, keys: usize) -> Result<f64> {
seed(conn, "bench_get", keys)?;
let kv = conn.kv_store("bench_get")?;
let start = Instant::now();
for i in 0..keys {
let _ = kv.get(&format!("k{i}"))?;
}
Ok(start.elapsed().as_secs_f64())
}
fn bench_delete(conn: &Connection, keys: usize) -> Result<f64> {
seed(conn, "bench_delete", keys)?;
let kv = conn.kv_store("bench_delete")?;
let start = Instant::now();
for i in 0..keys {
let _ = kv.delete(&format!("k{i}"))?;
}
Ok(start.elapsed().as_secs_f64())
}
fn bench_pop(conn: &Connection, keys: usize) -> Result<f64> {
seed(conn, "bench_pop", keys)?;
let kv = conn.kv_store("bench_pop")?;
let start = Instant::now();
for _ in 0..keys {
let _ = kv.pop()?;
}
Ok(start.elapsed().as_secs_f64())
}
fn main() -> Result<()> {
let keys = key_count();
println!("\n=== KV Store write benchmark ({keys} keys, batch size {BATCH}) ===");
let db_path = std::env::temp_dir().join("kv_benchmark.hyper");
let hyper = HyperProcess::new(None, None)?;
let conn = Connection::new(&hyper, &db_path, CreateMode::CreateAndReplace)?;
let single_secs = bench_single(&conn, keys)?;
throughput("single commit per set", keys, single_secs);
let batched_secs = bench_batched(&conn, keys)?;
throughput(&format!("batched ({BATCH}/txn)"), keys, batched_secs);
let overwrite_secs = bench_overwrite(&conn, keys)?;
throughput("overwrite existing keys", keys, overwrite_secs);
let get_secs = bench_get(&conn, keys)?;
throughput("get existing keys", keys, get_secs);
let delete_secs = bench_delete(&conn, keys)?;
throughput("delete existing keys", keys, delete_secs);
let pop_secs = bench_pop(&conn, keys)?;
throughput("pop (transactional)", keys, pop_secs);
if batched_secs > 0.0 {
println!(
"\n speedup (batched vs single): {:.2}x",
single_secs / batched_secs
);
}
if overwrite_secs > 0.0 {
println!(
" speedup (overwrite vs fresh insert): {:.2}x",
single_secs / overwrite_secs
);
}
Ok(())
}