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); |