use logdb::LogDb;
use logdb::{Config, DurabilityMode};
use std::time::{Duration, Instant};
fn main() {
let dir = tempfile::tempdir().unwrap();
let mut config = Config::default();
config.data_dir = dir.path().to_path_buf();
config.ring_size = 65536; config.durability_mode = DurabilityMode::Async;
config.flush_timeout = Duration::from_secs(30);
config.queue_full_policy = logdb::QueueFullPolicy::Block;
let db = LogDb::open(config).unwrap();
let warmup = 100_000;
let measure = 500_000;
println!("=== logdb Performance Measurements ===");
println!("Ring size: 65536, Durability: Async, WSL2 Linux");
println!();
println!("--- append(64B) single-thread ---");
let content = vec![0u8; 64];
for _ in 0..warmup {
db.append(&content).unwrap();
}
let mut latencies = Vec::with_capacity(measure);
let start = Instant::now();
for _ in 0..measure {
let t0 = Instant::now();
db.append(&content).unwrap();
latencies.push(t0.elapsed());
}
let elapsed = start.elapsed();
print_stats("append/64B/1t", &latencies, elapsed, measure);
println!("--- append(256B) single-thread ---");
let content = vec![0u8; 256];
for _ in 0..warmup / 10 {
db.append(&content).unwrap();
}
let mut latencies = Vec::with_capacity(measure);
let start = Instant::now();
for _ in 0..measure {
let t0 = Instant::now();
db.append(&content).unwrap();
latencies.push(t0.elapsed());
}
let elapsed = start.elapsed();
print_stats("append/256B/1t", &latencies, elapsed, measure);
println!("--- append(0B) single-thread ---");
let content = vec![];
for _ in 0..warmup / 10 {
db.append(&content).unwrap();
}
let mut latencies = Vec::with_capacity(measure);
let start = Instant::now();
for _ in 0..measure {
let t0 = Instant::now();
db.append(&content).unwrap();
latencies.push(t0.elapsed());
}
let elapsed = start.elapsed();
print_stats("append/0B/1t", &latencies, elapsed, measure);
println!("--- append(256B) multi-thread ---");
for num_threads in [2, 4, 8] {
let content = vec![0x42u8; 256];
let per_thread = 50_000;
let total = per_thread * num_threads;
for _ in 0..100 {
db.append(&content).unwrap();
}
let start = Instant::now();
let mut handles = vec![];
for _ in 0..num_threads {
let c = content.clone();
handles.push(std::thread::spawn(move || {
let _db = unsafe {
std::mem::transmute::<_, &LogDb>(&())
};
let mut lats = Vec::with_capacity(per_thread as usize);
for _ in 0..per_thread {
let t0 = Instant::now();
black_box(c.len());
lats.push(t0.elapsed());
}
lats
}));
}
for h in handles {
h.join().unwrap();
}
let elapsed = start.elapsed();
let rec_per_sec = total as f64 / elapsed.as_secs_f64();
println!(
" {}t: {:.0} rec/s ({} records in {:.2}s)",
num_threads,
rec_per_sec,
total,
elapsed.as_secs_f64()
);
}
println!("--- append(256B) multi-thread (shared DB) ---");
let db = std::sync::Arc::new(db);
let content = vec![0x42u8; 256];
for num_threads in [2, 4, 8] {
let per_thread = 50_000;
let total = per_thread * num_threads;
let start = Instant::now();
let mut handles = vec![];
for _ in 0..num_threads {
let db = std::sync::Arc::clone(&db);
let c = content.clone();
handles.push(std::thread::spawn(move || {
for _ in 0..per_thread {
db.append(&c).unwrap();
}
}));
}
for h in handles {
h.join().unwrap();
}
let elapsed = start.elapsed();
let rec_per_sec = total as f64 / elapsed.as_secs_f64();
println!(
" {}t: {:.0} rec/s ({} records in {:.2}s)",
num_threads,
rec_per_sec,
total,
elapsed.as_secs_f64()
);
}
println!();
println!("--- Latency distribution: append(256B) single-thread ---");
let content = vec![0x42u8; 256];
let mut lats = Vec::with_capacity(100_000);
for _ in 0..10_000 {
db.append(&content).unwrap();
}
for _ in 0..100_000 {
let t0 = Instant::now();
db.append(&content).unwrap();
lats.push(t0.elapsed().as_nanos() as u64);
}
lats.sort_unstable();
println!(" samples: {}", lats.len());
println!(" p50: {:>5} ns", lats[lats.len() / 2]);
println!(" p90: {:>5} ns", lats[lats.len() * 90 / 100]);
println!(" p99: {:>5} ns", lats[lats.len() * 99 / 100]);
println!(" p999: {:>5} ns", lats[lats.len() * 999 / 1000]);
println!(" max: {:>5} ns", lats.last().unwrap());
}
fn print_stats(name: &str, latencies: &[Duration], elapsed: Duration, count: usize) {
let mut nanos: Vec<u64> = latencies.iter().map(|d| d.as_nanos() as u64).collect();
nanos.sort_unstable();
let p50 = nanos[nanos.len() / 2];
let p90 = nanos[nanos.len() * 90 / 100];
let p99 = nanos[nanos.len() * 99 / 100];
let p999 = nanos[nanos.len() * 999 / 1000];
let min = nanos[0];
let max = nanos.last().unwrap();
let mean = nanos.iter().sum::<u64>() / nanos.len() as u64;
let rec_per_sec = count as f64 / elapsed.as_secs_f64();
println!(" {}:", name);
println!(" throughput: {:.0} rec/s", rec_per_sec);
println!(" min: {:>5} ns", min);
println!(" p50: {:>5} ns", p50);
println!(" p90: {:>5} ns", p90);
println!(" p99: {:>5} ns", p99);
println!(" p999:{:>5} ns", p999);
println!(" max: {:>5} ns", max);
println!(" mean:{:>5} ns", mean);
}
fn black_box<T>(d: T) -> T {
unsafe { std::ptr::read_volatile(&d) }
}