337 lines
13 KiB
Rust
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); |