Files
rustytorch/integration_tests/benches/memory_utilization.rs
T
2026-03-04 00:08:42 +00:00

409 lines
13 KiB
Rust

/*!
Memory Utilization Benchmarks
Benchmarks measuring memory usage patterns, efficiency, and optimization
across different scenarios. These benchmarks help identify memory bottlenecks
and validate memory management strategies.
## Benchmark Categories
1. **Memory Allocation**: Allocation and deallocation patterns
2. **Memory Fragmentation**: Fragmentation impact on performance
3. **Cache Efficiency**: Memory access pattern optimization
4. **GPU Memory**: GPU memory allocation and transfer patterns
5. **Memory Pools**: Memory pool effectiveness
6. **Large Model Handling**: Memory usage with large models
## Usage
```bash
# Run memory utilization benchmarks
cargo bench --bench memory_utilization
# Run with memory profiling
RUST_LOG=debug cargo bench --bench memory_utilization
# Generate memory usage report
cargo bench --bench memory_utilization -- --memory-report
```
*/
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId, Throughput};
use rustytorch_integration_tests::*;
use std::time::Duration;
use tokio::runtime::Runtime;
fn benchmark_memory_allocation_patterns(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("memory_allocation");
group.measurement_time(Duration::from_secs(10));
let allocation_sizes = vec![1024, 4096, 16384, 65536, 262144]; // 1KB to 256KB
for size in allocation_sizes {
group.throughput(Throughput::Bytes(size as u64));
group.bench_with_input(
BenchmarkId::new("allocate_deallocate", size),
&size,
|b, &size| {
b.to_async(&rt).iter(|| async {
benchmark_allocation_cycle(black_box(size)).await
});
},
);
group.bench_with_input(
BenchmarkId::new("bulk_allocation", size),
&size,
|b, &size| {
b.to_async(&rt).iter(|| async {
benchmark_bulk_allocation(black_box(size)).await
});
},
);
}
group.finish();
}
fn benchmark_memory_fragmentation(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("memory_fragmentation");
group.measurement_time(Duration::from_secs(15));
let fragmentation_scenarios = vec![
("sequential", false),
("fragmented", true),
];
for (scenario, fragmented) in fragmentation_scenarios {
group.bench_with_input(
BenchmarkId::new("allocation_pattern", scenario),
&fragmented,
|b, &fragmented| {
b.to_async(&rt).iter(|| async {
benchmark_fragmentation_pattern(black_box(fragmented)).await
});
},
);
}
group.finish();
}
fn benchmark_cache_efficiency(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("cache_efficiency");
group.measurement_time(Duration::from_secs(12));
let access_patterns = vec![
("sequential", AccessPattern::Sequential),
("random", AccessPattern::Random),
("strided", AccessPattern::Strided),
];
let data_sizes = vec![1024 * 1024, 10 * 1024 * 1024]; // 1MB, 10MB
for (pattern_name, pattern) in access_patterns {
for data_size in &data_sizes {
group.throughput(Throughput::Bytes(*data_size as u64));
group.bench_with_input(
BenchmarkId::new(pattern_name, format!("{}MB", data_size / (1024 * 1024))),
&(*data_size, pattern),
|b, &(data_size, pattern)| {
b.to_async(&rt).iter(|| async {
benchmark_memory_access_pattern(black_box(data_size), black_box(pattern)).await
});
},
);
}
}
group.finish();
}
fn benchmark_gpu_memory_operations(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("gpu_memory");
group.measurement_time(Duration::from_secs(20));
group.sample_size(20); // Fewer samples for GPU operations
let transfer_sizes = vec![1024 * 1024, 16 * 1024 * 1024, 64 * 1024 * 1024]; // 1MB, 16MB, 64MB
for size in transfer_sizes {
group.throughput(Throughput::Bytes(size as u64));
group.bench_with_input(
BenchmarkId::new("host_to_device", format!("{}MB", size / (1024 * 1024))),
&size,
|b, &size| {
b.to_async(&rt).iter(|| async {
benchmark_host_to_device_transfer(black_box(size)).await
});
},
);
group.bench_with_input(
BenchmarkId::new("device_to_host", format!("{}MB", size / (1024 * 1024))),
&size,
|b, &size| {
b.to_async(&rt).iter(|| async {
benchmark_device_to_host_transfer(black_box(size)).await
});
},
);
group.bench_with_input(
BenchmarkId::new("device_to_device", format!("{}MB", size / (1024 * 1024))),
&size,
|b, &size| {
b.to_async(&rt).iter(|| async {
benchmark_device_to_device_transfer(black_box(size)).await
});
},
);
}
group.finish();
}
fn benchmark_memory_pools(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("memory_pools");
group.measurement_time(Duration::from_secs(15));
let pool_configurations = vec![
("no_pool", PoolConfig::None),
("small_pool", PoolConfig::Small),
("large_pool", PoolConfig::Large),
("adaptive_pool", PoolConfig::Adaptive),
];
for (config_name, pool_config) in pool_configurations {
group.bench_with_input(
BenchmarkId::new("pooled_allocation", config_name),
&pool_config,
|b, &pool_config| {
b.to_async(&rt).iter(|| async {
benchmark_pooled_allocation(black_box(pool_config)).await
});
},
);
}
group.finish();
}
fn benchmark_large_model_memory(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("large_model_memory");
group.measurement_time(Duration::from_secs(30));
group.sample_size(10); // Very few samples for large model tests
let model_sizes = vec![
("small", 100 * 1024 * 1024), // 100MB
("medium", 1024 * 1024 * 1024), // 1GB
("large", 4 * 1024 * 1024 * 1024), // 4GB
];
for (size_name, size_bytes) in model_sizes {
group.throughput(Throughput::Bytes(size_bytes as u64));
group.bench_with_input(
BenchmarkId::new("model_loading", size_name),
&size_bytes,
|b, &size_bytes| {
b.to_async(&rt).iter(|| async {
benchmark_large_model_loading(black_box(size_bytes)).await
});
},
);
group.bench_with_input(
BenchmarkId::new("model_inference", size_name),
&size_bytes,
|b, &size_bytes| {
b.to_async(&rt).iter(|| async {
benchmark_large_model_inference(black_box(size_bytes)).await
});
},
);
}
group.finish();
}
fn benchmark_memory_pressure(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("memory_pressure");
group.measurement_time(Duration::from_secs(25));
let pressure_levels = vec![
("low", 0.3), // 30% memory usage
("medium", 0.6), // 60% memory usage
("high", 0.9), // 90% memory usage
];
for (level_name, pressure_ratio) in pressure_levels {
group.bench_with_input(
BenchmarkId::new("under_pressure", level_name),
&pressure_ratio,
|b, &pressure_ratio| {
b.to_async(&rt).iter(|| async {
benchmark_memory_under_pressure(black_box(pressure_ratio)).await
});
},
);
}
group.finish();
}
// Helper types and enums
#[derive(Debug, Clone, Copy)]
enum AccessPattern {
Sequential,
Random,
Strided,
}
#[derive(Debug, Clone, Copy)]
enum PoolConfig {
None,
Small,
Large,
Adaptive,
}
// Mock benchmark implementations
async fn benchmark_allocation_cycle(size: usize) -> usize {
// Simulate allocation and deallocation
let alloc_time = Duration::from_nanos(100 + (size / 1024) as u64 * 10);
tokio::time::sleep(alloc_time).await;
let dealloc_time = Duration::from_nanos(50 + (size / 2048) as u64 * 5);
tokio::time::sleep(dealloc_time).await;
size
}
async fn benchmark_bulk_allocation(size: usize) -> usize {
// Simulate bulk allocation of multiple blocks
let num_blocks = 100;
let total_size = size * num_blocks;
let bulk_alloc_time = Duration::from_nanos(500 + (total_size / 1024) as u64 * 5);
tokio::time::sleep(bulk_alloc_time).await;
total_size
}
async fn benchmark_fragmentation_pattern(fragmented: bool) -> usize {
let allocations = if fragmented { 200 } else { 50 };
let base_time = if fragmented { 2000 } else { 500 }; // Fragmented allocations are slower
tokio::time::sleep(Duration::from_nanos(base_time + allocations * 10)).await;
allocations
}
async fn benchmark_memory_access_pattern(data_size: usize, pattern: AccessPattern) -> usize {
let access_time = match pattern {
AccessPattern::Sequential => data_size / (1024 * 1024), // Fast sequential access
AccessPattern::Random => data_size / (512 * 1024), // Slower random access
AccessPattern::Strided => data_size / (256 * 1024), // Slowest strided access
};
tokio::time::sleep(Duration::from_micros(access_time as u64)).await;
data_size
}
async fn benchmark_host_to_device_transfer(size: usize) -> usize {
// Simulate PCIe transfer bandwidth (~16 GB/s for PCIe 4.0 x16)
let transfer_time_ns = (size as u64 * 1000) / 16; // nanoseconds
tokio::time::sleep(Duration::from_nanos(transfer_time_ns)).await;
size
}
async fn benchmark_device_to_host_transfer(size: usize) -> usize {
// Similar to host-to-device but slightly slower
let transfer_time_ns = (size as u64 * 1000) / 14;
tokio::time::sleep(Duration::from_nanos(transfer_time_ns)).await;
size
}
async fn benchmark_device_to_device_transfer(size: usize) -> usize {
// Much faster GPU-to-GPU transfer
let transfer_time_ns = (size as u64 * 1000) / 400; // ~400 GB/s for high-end GPUs
tokio::time::sleep(Duration::from_nanos(transfer_time_ns)).await;
size
}
async fn benchmark_pooled_allocation(pool_config: PoolConfig) -> usize {
let (alloc_time, efficiency) = match pool_config {
PoolConfig::None => (1000, 1.0), // No pooling, baseline
PoolConfig::Small => (200, 0.8), // Small pool, fast but less efficient
PoolConfig::Large => (150, 0.95), // Large pool, very efficient
PoolConfig::Adaptive => (100, 0.98), // Adaptive pool, best of both
};
tokio::time::sleep(Duration::from_nanos(alloc_time)).await;
(1024.0 * efficiency) as usize // Effective allocation size
}
async fn benchmark_large_model_loading(size_bytes: usize) -> usize {
// Simulate loading time based on disk I/O and memory bandwidth
let loading_time_ms = (size_bytes / (1024 * 1024)) as u64 * 10; // ~100 MB/s loading
tokio::time::sleep(Duration::from_millis(loading_time_ms)).await;
size_bytes
}
async fn benchmark_large_model_inference(size_bytes: usize) -> usize {
// Simulate inference time scaling with model size
let inference_base_time = 100; // Base inference time in milliseconds
let size_factor = (size_bytes / (100 * 1024 * 1024)) as u64; // Per 100MB
let total_time = inference_base_time + size_factor * 50;
tokio::time::sleep(Duration::from_millis(total_time)).await;
size_bytes
}
async fn benchmark_memory_under_pressure(pressure_ratio: f64) -> usize {
// Simulate performance degradation under memory pressure
let base_time = 100;
let pressure_multiplier = if pressure_ratio > 0.8 {
3.0 // Significant slowdown at high pressure
} else if pressure_ratio > 0.6 {
1.5 // Moderate slowdown at medium pressure
} else {
1.0 // No slowdown at low pressure
};
let total_time = (base_time as f64 * pressure_multiplier) as u64;
tokio::time::sleep(Duration::from_millis(total_time)).await;
(1024.0 / pressure_multiplier) as usize // Effective processing amount
}
criterion_group!(
benches,
benchmark_memory_allocation_patterns,
benchmark_memory_fragmentation,
benchmark_cache_efficiency,
benchmark_gpu_memory_operations,
benchmark_memory_pools,
benchmark_large_model_memory,
benchmark_memory_pressure
);
criterion_main!(benches);