Files
herodb/benches/memory_profile.rs
Maxime Van Hees 9136e5f3c0 benchmarking
2025-10-30 11:17:26 +01:00

337 lines
13 KiB
Rust

// benches/memory_profile.rs
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId, BatchSize};
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
mod common;
use common::*;
// Simple memory tracking allocator
struct TrackingAllocator;
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
static DEALLOCATED: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for TrackingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ret = System.alloc(layout);
if !ret.is_null() {
let size = layout.size();
ALLOCATED.fetch_add(size, Ordering::SeqCst);
ALLOC_COUNT.fetch_add(1, Ordering::SeqCst);
// Update peak if necessary
let current = ALLOCATED.load(Ordering::SeqCst) - DEALLOCATED.load(Ordering::SeqCst);
let mut peak = PEAK.load(Ordering::SeqCst);
while current > peak {
match PEAK.compare_exchange_weak(peak, current, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => break,
Err(x) => peak = x,
}
}
}
ret
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
DEALLOCATED.fetch_add(layout.size(), Ordering::SeqCst);
}
}
#[global_allocator]
static GLOBAL: TrackingAllocator = TrackingAllocator;
/// Reset memory tracking counters
fn reset_memory_tracking() {
ALLOCATED.store(0, Ordering::SeqCst);
DEALLOCATED.store(0, Ordering::SeqCst);
PEAK.store(0, Ordering::SeqCst);
ALLOC_COUNT.store(0, Ordering::SeqCst);
}
/// Get current memory stats
fn get_memory_stats() -> (usize, usize, usize) {
let allocated = ALLOCATED.load(Ordering::SeqCst);
let deallocated = DEALLOCATED.load(Ordering::SeqCst);
let peak = PEAK.load(Ordering::SeqCst);
let alloc_count = ALLOC_COUNT.load(Ordering::SeqCst);
let current = allocated.saturating_sub(deallocated);
(current, peak, alloc_count)
}
/// Profile memory usage for single SET operations
fn profile_memory_set(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/set");
for backend_type in BackendType::all() {
group.bench_with_input(
BenchmarkId::new(backend_type.name(), "100bytes"),
&backend_type,
|b, &backend_type| {
b.iter_batched(
|| {
reset_memory_tracking();
let backend = BenchmarkBackend::new(backend_type).unwrap();
let mut generator = DataGenerator::new(42);
let key = generator.generate_key("bench:key", 0);
let value = generator.generate_value(100);
(backend, key, value)
},
|(backend, key, value)| {
backend.storage.set(key, value).unwrap();
let (current, peak, allocs) = get_memory_stats();
println!("{}: current={}, peak={}, allocs={}",
backend.name(), current, peak, allocs);
},
BatchSize::SmallInput
);
}
);
}
group.finish();
}
/// Profile memory usage for single GET operations
fn profile_memory_get(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/get");
for backend_type in BackendType::all() {
let backend = setup_populated_backend(backend_type, 1_000, 100)
.expect("Failed to setup backend");
let generator = DataGenerator::new(42);
group.bench_with_input(
BenchmarkId::new(backend.name(), "100bytes"),
&backend,
|b, backend| {
b.iter_batched(
|| {
reset_memory_tracking();
generator.generate_key("bench:key", 0)
},
|key| {
backend.storage.get(&key).unwrap();
let (current, peak, allocs) = get_memory_stats();
println!("{}: current={}, peak={}, allocs={}",
backend.name(), current, peak, allocs);
},
BatchSize::SmallInput
);
}
);
}
group.finish();
}
/// Profile memory usage for bulk insert operations
fn profile_memory_bulk_insert(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/bulk_insert");
for size in [100, 1_000] {
for backend_type in BackendType::all() {
group.bench_with_input(
BenchmarkId::new(format!("{}/size", backend_type.name()), size),
&(backend_type, size),
|b, &(backend_type, size)| {
b.iter_batched(
|| {
reset_memory_tracking();
let backend = BenchmarkBackend::new(backend_type).unwrap();
let mut generator = DataGenerator::new(42);
let data = generator.generate_string_pairs(size, 100);
(backend, data)
},
|(backend, data)| {
for (key, value) in data {
backend.storage.set(key, value).unwrap();
}
let (current, peak, allocs) = get_memory_stats();
println!("{} (n={}): current={}, peak={}, allocs={}, bytes_per_record={}",
backend.name(), size, current, peak, allocs, peak / size);
},
BatchSize::SmallInput
);
}
);
}
}
group.finish();
}
/// Profile memory usage for hash operations
fn profile_memory_hash_ops(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/hash_ops");
for backend_type in BackendType::all() {
group.bench_with_input(
BenchmarkId::new(backend_type.name(), "hset"),
&backend_type,
|b, &backend_type| {
b.iter_batched(
|| {
reset_memory_tracking();
let backend = BenchmarkBackend::new(backend_type).unwrap();
let mut generator = DataGenerator::new(42);
let key = generator.generate_key("bench:hash", 0);
let fields = vec![
("field1".to_string(), generator.generate_value(100)),
("field2".to_string(), generator.generate_value(100)),
("field3".to_string(), generator.generate_value(100)),
];
(backend, key, fields)
},
|(backend, key, fields)| {
backend.storage.hset(&key, fields).unwrap();
let (current, peak, allocs) = get_memory_stats();
println!("{}: current={}, peak={}, allocs={}",
backend.name(), current, peak, allocs);
},
BatchSize::SmallInput
);
}
);
}
group.finish();
}
/// Profile memory usage for list operations
fn profile_memory_list_ops(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/list_ops");
for backend_type in BackendType::all() {
group.bench_with_input(
BenchmarkId::new(backend_type.name(), "rpush"),
&backend_type,
|b, &backend_type| {
b.iter_batched(
|| {
reset_memory_tracking();
let backend = BenchmarkBackend::new(backend_type).unwrap();
let mut generator = DataGenerator::new(42);
let key = generator.generate_key("bench:list", 0);
let elements = vec![
generator.generate_value(100),
generator.generate_value(100),
generator.generate_value(100),
];
(backend, key, elements)
},
|(backend, key, elements)| {
backend.storage.rpush(&key, elements).unwrap();
let (current, peak, allocs) = get_memory_stats();
println!("{}: current={}, peak={}, allocs={}",
backend.name(), current, peak, allocs);
},
BatchSize::SmallInput
);
}
);
}
group.finish();
}
/// Profile memory usage for scan operations
fn profile_memory_scan(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/scan");
for size in [1_000, 10_000] {
for backend_type in BackendType::all() {
let backend = setup_populated_backend(backend_type, size, 100)
.expect("Failed to setup backend");
group.bench_with_input(
BenchmarkId::new(format!("{}/size", backend.name()), size),
&backend,
|b, backend| {
b.iter(|| {
reset_memory_tracking();
let mut cursor = 0u64;
let mut total = 0;
loop {
let (next_cursor, items) = backend.storage
.scan(cursor, None, Some(100))
.unwrap();
total += items.len();
if next_cursor == 0 {
break;
}
cursor = next_cursor;
}
let (current, peak, allocs) = get_memory_stats();
println!("{} (n={}): scanned={}, current={}, peak={}, allocs={}",
backend.name(), size, total, current, peak, allocs);
total
});
}
);
}
}
group.finish();
}
/// Profile memory efficiency (bytes per record stored)
fn profile_memory_efficiency(c: &mut Criterion) {
let mut group = c.benchmark_group("memory_profile/efficiency");
for size in [1_000, 10_000] {
for backend_type in BackendType::all() {
group.bench_with_input(
BenchmarkId::new(format!("{}/size", backend_type.name()), size),
&(backend_type, size),
|b, &(backend_type, size)| {
b.iter_batched(
|| {
reset_memory_tracking();
let backend = BenchmarkBackend::new(backend_type).unwrap();
let mut generator = DataGenerator::new(42);
let data = generator.generate_string_pairs(size, 100);
(backend, data)
},
|(backend, data)| {
let data_size: usize = data.iter()
.map(|(k, v)| k.len() + v.len())
.sum();
for (key, value) in data {
backend.storage.set(key, value).unwrap();
}
let (current, peak, allocs) = get_memory_stats();
let overhead_pct = ((peak as f64 - data_size as f64) / data_size as f64) * 100.0;
println!("{} (n={}): data_size={}, peak={}, overhead={:.1}%, bytes_per_record={}, allocs={}",
backend.name(), size, data_size, peak, overhead_pct,
peak / size, allocs);
},
BatchSize::SmallInput
);
}
);
}
}
group.finish();
}
criterion_group!(
benches,
profile_memory_set,
profile_memory_get,
profile_memory_bulk_insert,
profile_memory_hash_ops,
profile_memory_list_ops,
profile_memory_scan,
profile_memory_efficiency,
);
criterion_main!(benches);