#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MemoryEstimate {
pub primary_bytes: u64,
pub auxiliary_bytes: u64,
pub peak_bytes: u64,
pub confidence: f32,
}
impl Default for MemoryEstimate {
fn default() -> Self {
Self::new()
}
}
impl MemoryEstimate {
#[must_use]
pub fn new() -> Self {
Self {
primary_bytes: 0,
auxiliary_bytes: 0,
peak_bytes: 0,
confidence: 1.0,
}
}
#[must_use]
pub fn primary(bytes: u64) -> Self {
Self {
primary_bytes: bytes,
auxiliary_bytes: 0,
peak_bytes: bytes,
confidence: 1.0,
}
}
#[must_use]
pub fn with_primary(mut self, bytes: u64) -> Self {
self.primary_bytes = bytes;
self.update_peak();
self
}
#[must_use]
pub fn with_auxiliary(mut self, bytes: u64) -> Self {
self.auxiliary_bytes = bytes;
self.update_peak();
self
}
#[must_use]
pub fn with_peak(mut self, bytes: u64) -> Self {
self.peak_bytes = bytes;
self
}
#[must_use]
pub fn with_confidence(mut self, confidence: f32) -> Self {
self.confidence = confidence.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn total_bytes(&self) -> u64 {
self.primary_bytes.saturating_add(self.auxiliary_bytes)
}
fn update_peak(&mut self) {
let total = self.total_bytes();
if self.peak_bytes < total {
self.peak_bytes = total;
}
}
#[must_use]
pub fn summary(&self) -> String {
format!(
"primary={}, auxiliary={}, peak={}, confidence={:.0}%",
format_bytes(self.primary_bytes),
format_bytes(self.auxiliary_bytes),
format_bytes(self.peak_bytes),
self.confidence * 100.0
)
}
#[must_use]
pub fn combine(&self, other: &MemoryEstimate) -> Self {
Self {
primary_bytes: self.primary_bytes.saturating_add(other.primary_bytes),
auxiliary_bytes: self.auxiliary_bytes.saturating_add(other.auxiliary_bytes),
peak_bytes: self.peak_bytes.saturating_add(other.peak_bytes),
confidence: (self.confidence + other.confidence) / 2.0,
}
}
#[must_use]
pub fn scale(&self, factor: f64) -> Self {
Self {
primary_bytes: (self.primary_bytes as f64 * factor) as u64,
auxiliary_bytes: (self.auxiliary_bytes as f64 * factor) as u64,
peak_bytes: (self.peak_bytes as f64 * factor) as u64,
confidence: self.confidence,
}
}
}
pub trait MemoryEstimator: Send + Sync {
fn estimate(&self) -> MemoryEstimate;
fn name(&self) -> &str;
fn estimate_for(&self, element_count: usize) -> MemoryEstimate {
let _ = element_count;
self.estimate()
}
}
#[derive(Debug, Clone)]
pub struct LinearEstimator {
pub name: String,
pub bytes_per_element: usize,
pub fixed_overhead: usize,
pub auxiliary_per_element: usize,
}
impl LinearEstimator {
#[must_use]
pub fn new(name: impl Into<String>, bytes_per_element: usize) -> Self {
Self {
name: name.into(),
bytes_per_element,
fixed_overhead: 0,
auxiliary_per_element: 0,
}
}
#[must_use]
pub fn with_overhead(mut self, bytes: usize) -> Self {
self.fixed_overhead = bytes;
self
}
#[must_use]
pub fn with_auxiliary(mut self, bytes_per_element: usize) -> Self {
self.auxiliary_per_element = bytes_per_element;
self
}
}
impl MemoryEstimator for LinearEstimator {
fn estimate(&self) -> MemoryEstimate {
MemoryEstimate::new()
.with_primary(self.fixed_overhead as u64)
.with_confidence(0.9)
}
fn name(&self) -> &str {
&self.name
}
fn estimate_for(&self, element_count: usize) -> MemoryEstimate {
let primary = self.fixed_overhead + (self.bytes_per_element * element_count);
let auxiliary = self.auxiliary_per_element * element_count;
MemoryEstimate::new()
.with_primary(primary as u64)
.with_auxiliary(auxiliary as u64)
.with_confidence(0.9)
}
}
fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_estimate_new() {
let estimate = MemoryEstimate::new();
assert_eq!(estimate.total_bytes(), 0);
assert!((estimate.confidence - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_memory_estimate_primary() {
let estimate = MemoryEstimate::primary(1024);
assert_eq!(estimate.primary_bytes, 1024);
assert_eq!(estimate.total_bytes(), 1024);
assert_eq!(estimate.peak_bytes, 1024);
}
#[test]
fn test_memory_estimate_builder() {
let estimate = MemoryEstimate::new()
.with_primary(1024)
.with_auxiliary(512)
.with_confidence(0.8);
assert_eq!(estimate.primary_bytes, 1024);
assert_eq!(estimate.auxiliary_bytes, 512);
assert_eq!(estimate.total_bytes(), 1536);
assert!((estimate.confidence - 0.8).abs() < f32::EPSILON);
}
#[test]
fn test_memory_estimate_combine() {
let a = MemoryEstimate::new().with_primary(1000).with_auxiliary(500);
let b = MemoryEstimate::new()
.with_primary(2000)
.with_auxiliary(1000);
let combined = a.combine(&b);
assert_eq!(combined.primary_bytes, 3000);
assert_eq!(combined.auxiliary_bytes, 1500);
}
#[test]
fn test_memory_estimate_scale() {
let estimate = MemoryEstimate::new().with_primary(1000);
let scaled = estimate.scale(2.0);
assert_eq!(scaled.primary_bytes, 2000);
}
#[test]
fn test_linear_estimator() {
let estimator = LinearEstimator::new("test", 64)
.with_overhead(1024)
.with_auxiliary(16);
let estimate = estimator.estimate_for(100);
assert_eq!(estimate.primary_bytes, 1024 + 64 * 100);
assert_eq!(estimate.auxiliary_bytes, 16 * 100);
}
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(512), "512 B");
assert_eq!(format_bytes(1024), "1.00 KB");
assert_eq!(format_bytes(1_500_000), "1.43 MB");
}
}