use super::{
buffer_bytes, reconcile_buffer_capacity, replacement, MemoryBudget, MemoryError,
MemoryReservation,
};
#[derive(Debug)]
pub struct BudgetedVec<T> {
values: Vec<T>,
memory: MemoryReservation,
}
impl<T> BudgetedVec<T> {
pub fn new(budget: &MemoryBudget) -> Self {
Self {
values: Vec::new(),
memory: budget.empty_reservation(),
}
}
pub fn capacity(&self) -> usize {
self.values.capacity()
}
pub fn budget(&self) -> &MemoryBudget {
self.memory.budget()
}
pub fn reserve(&mut self, additional: usize) -> Result<(), MemoryError> {
let required = self
.values
.len()
.checked_add(additional)
.ok_or(MemoryError::SizeOverflow)?;
if required <= self.values.capacity() {
return Ok(());
}
let (capacity, memory) = replacement::<T>(self.memory.budget(), self.capacity(), required)?;
let mut buffer = Self {
values: Vec::new(),
memory,
};
buffer.values.try_reserve_exact(capacity)?;
self.replace_buffer(buffer)
}
fn replace_buffer(&mut self, mut buffer: Self) -> Result<(), MemoryError> {
reconcile_buffer_capacity::<T>(&mut buffer.memory, buffer.values.capacity())?;
buffer.values.append(&mut self.values);
*self = buffer;
Ok(())
}
pub fn push(&mut self, value: T) -> Result<(), MemoryError> {
self.reserve(1)?;
self.values.push(value);
Ok(())
}
pub fn clear(&mut self) {
self.values.clear();
}
pub fn truncate(&mut self, len: usize) {
self.values.truncate(len);
}
pub fn shrink_to_fit(&mut self) -> Result<(), MemoryError> {
let capacity = self.values.len();
if capacity == self.values.capacity() {
return Ok(());
}
let memory = self.memory.budget().reserve(buffer_bytes::<T>(capacity)?)?;
let mut buffer = Self {
values: Vec::new(),
memory,
};
buffer.values.try_reserve_exact(capacity)?;
self.replace_buffer(buffer)
}
pub fn pop(&mut self) -> Option<T> {
self.values.pop()
}
pub fn into_parts(self) -> (Vec<T>, MemoryReservation) {
(self.values, self.memory)
}
}
impl<T> std::ops::Deref for BudgetedVec<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.values
}
}
impl<T: Copy> BudgetedVec<T> {
pub fn extend_from_slice(&mut self, values: &[T]) -> Result<(), MemoryError> {
self.reserve(values.len())?;
self.values.extend_from_slice(values);
Ok(())
}
}
impl<T> std::ops::DerefMut for BudgetedVec<T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.values
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extending_a_slice_reserves_before_mutating_and_preserves_failed_inputs() {
let budget = MemoryBudget::new(16);
let mut values = BudgetedVec::new(&budget);
values.extend_from_slice(&[1_u8, 2, 3, 4]).unwrap();
let retained = budget.used();
assert!(matches!(
values.extend_from_slice(&[9; 32]),
Err(MemoryError::Limit { .. })
));
assert_eq!(&*values, &[1, 2, 3, 4]);
assert_eq!(budget.used(), retained);
values.extend_from_slice(&[5, 6]).unwrap();
assert_eq!(&*values, &[1, 2, 3, 4, 5, 6]);
assert_eq!(budget.used(), values.capacity());
drop(values);
assert_eq!(budget.used(), 0);
}
#[test]
fn shrinking_releases_excess_capacity_after_charging_both_buffers() {
let budget = MemoryBudget::new(32);
let mut values = BudgetedVec::new(&budget);
values
.extend_from_slice(&[1_u8, 2, 3, 4, 5, 6, 7, 8])
.unwrap();
values.truncate(2);
values.shrink_to_fit().unwrap();
assert_eq!(&*values, &[1, 2]);
assert_eq!(values.capacity(), 2);
assert_eq!(budget.used(), 2);
assert_eq!(budget.peak(), 10);
values.shrink_to_fit().unwrap();
assert_eq!(budget.used(), 2);
}
#[test]
fn failed_shrinking_preserves_the_buffer_and_empty_shrinking_needs_no_headroom() {
let budget = MemoryBudget::new(8);
let mut values = BudgetedVec::new(&budget);
values
.extend_from_slice(&[1_u8, 2, 3, 4, 5, 6, 7, 8])
.unwrap();
values.truncate(2);
assert!(matches!(
values.shrink_to_fit(),
Err(MemoryError::Limit { .. })
));
assert_eq!(&*values, &[1, 2]);
assert_eq!(values.capacity(), 8);
assert_eq!(budget.used(), 8);
values.clear();
values.shrink_to_fit().unwrap();
assert_eq!(values.capacity(), 0);
assert_eq!(budget.used(), 0);
}
#[test]
fn oversized_replacement_is_charged_before_moving_the_original_values() {
for reject in [false, true] {
let mut original = vec![11_u32, 22];
original.reserve_exact(6);
let replacement = Vec::with_capacity(12);
let original_capacity = original.capacity();
let original_bytes = original_capacity * size_of::<u32>();
let replacement_bytes = replacement.capacity() * size_of::<u32>();
let budget =
MemoryBudget::new(original_bytes + replacement_bytes - usize::from(reject));
let mut values = BudgetedVec {
values: original,
memory: budget.reserve(original_bytes).unwrap(),
};
let buffer = BudgetedVec {
values: replacement,
memory: budget.reserve(values.len() * size_of::<u32>()).unwrap(),
};
let result = values.replace_buffer(buffer);
assert_eq!(&*values, &[11, 22]);
if reject {
assert!(matches!(result, Err(MemoryError::Limit { .. })));
assert_eq!(values.capacity(), original_capacity);
assert_eq!(budget.used(), original_bytes);
} else {
result.unwrap();
assert_eq!(budget.used(), replacement_bytes);
assert_eq!(budget.peak(), original_bytes + replacement_bytes);
}
drop(values);
assert_eq!(budget.used(), 0);
}
}
#[test]
fn shrinking_moves_owned_values_without_copying_or_dropping_survivors() {
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
struct Item(Arc<AtomicUsize>);
impl Drop for Item {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
let budget = MemoryBudget::new(1024);
let dropped = Arc::new(AtomicUsize::new(0));
let mut values = BudgetedVec::new(&budget);
for _ in 0..4 {
values.push(Item(Arc::clone(&dropped))).unwrap();
}
values.truncate(1);
assert_eq!(dropped.load(Ordering::Relaxed), 3);
values.shrink_to_fit().unwrap();
assert_eq!(dropped.load(Ordering::Relaxed), 3);
drop(values);
assert_eq!(dropped.load(Ordering::Relaxed), 4);
assert_eq!(budget.used(), 0);
}
}