use super::{calculate_padding, MemoryPool, Slice, SliceBinding, SliceHandle, SliceId};
use crate::{
memory_management::{MemoryLock, MemoryUsage},
storage::{ComputeStorage, StorageHandle, StorageId, StorageUtilization},
};
use alloc::vec::Vec;
use hashbrown::{HashMap, HashSet};
pub struct ExclusiveMemoryPool {
pages: HashMap<StorageId, MemoryPage>,
slices: HashMap<SliceId, Slice>,
ring_buffer: Vec<StorageId>,
index: usize,
max_page_size: u64,
alignment: u64,
dealloc_period: u64,
last_dealloc: u64,
}
struct MemoryPage {
slice_id: SliceId,
dealloc_mark: bool,
}
impl ExclusiveMemoryPool {
pub(crate) fn new(page_size: u64, alignment: u64, dealloc_period: u64) -> Self {
assert_eq!(page_size % alignment, 0);
Self {
pages: HashMap::new(),
slices: HashMap::new(),
ring_buffer: Vec::new(),
index: 0,
max_page_size: page_size,
alignment,
dealloc_period,
last_dealloc: 0,
}
}
fn get_free_page(&mut self, locked: Option<&MemoryLock>) -> Option<SliceId> {
for _ in 0..self.ring_buffer.len() {
let storage_id = &self.ring_buffer[self.index];
if let Some(locked) = locked.as_ref() {
if locked.is_locked(storage_id) {
continue;
}
}
let page = self.pages.get_mut(storage_id).unwrap();
page.dealloc_mark = false;
let slice = self.slices.get(&page.slice_id).unwrap();
self.index = (self.index + 1) % self.ring_buffer.len();
if slice.handle.is_free() {
return Some(page.slice_id);
}
}
None
}
}
impl MemoryPool for ExclusiveMemoryPool {
fn get(&self, binding: &SliceBinding) -> Option<&StorageHandle> {
self.slices.get(binding.id()).map(|s| &s.storage)
}
fn reserve<Storage: ComputeStorage>(
&mut self,
storage: &mut Storage,
size: u64,
exclude: Option<&MemoryLock>,
) -> SliceHandle {
let page = self.get_free_page(exclude);
let slice_id = if let Some(page) = page {
page
} else {
*self.alloc(storage, self.max_page_size).id()
};
let padding = calculate_padding(size, self.alignment);
let slice = self.slices.get_mut(&slice_id).unwrap();
slice.storage.utilization = StorageUtilization { offset: 0, size };
slice.padding = padding;
slice.handle.clone()
}
fn alloc<Storage: ComputeStorage>(&mut self, storage: &mut Storage, size: u64) -> SliceHandle {
let storage = storage.alloc(size);
self.ring_buffer.push(storage.id);
let handle = SliceHandle::new();
let padding = calculate_padding(size, self.alignment);
let slice = Slice::new(storage.clone(), handle, padding);
let handle_slice = slice.handle.clone();
let slice_id = *slice.handle.id();
self.pages.insert(
storage.id,
MemoryPage {
slice_id,
dealloc_mark: false,
},
);
self.slices.insert(slice_id, slice);
handle_slice
}
fn get_memory_usage(&self) -> MemoryUsage {
let used_slices: Vec<_> = self
.slices
.values()
.filter(|slice| !slice.is_free())
.collect();
MemoryUsage {
number_allocs: used_slices.len() as u64,
bytes_in_use: used_slices.iter().map(|s| s.storage.size()).sum(),
bytes_padding: used_slices.iter().map(|s| s.padding).sum(),
bytes_reserved: self.pages.len() as u64 * self.max_page_size,
}
}
fn max_alloc_size(&self) -> u64 {
self.max_page_size
}
fn cleanup<Storage: ComputeStorage>(&mut self, storage: &mut Storage, alloc_nr: u64) {
let elapsed = alloc_nr - self.last_dealloc;
if elapsed < self.dealloc_period {
return;
}
self.last_dealloc = alloc_nr;
let deallocations: HashSet<_> = self
.pages
.iter_mut()
.filter_map(|(storage_id, page)| {
let slice = self.slices.get(&page.slice_id).unwrap();
if slice.is_free() {
if !page.dealloc_mark {
page.dealloc_mark = true;
None
} else {
Some(*storage_id)
}
} else {
None
}
})
.collect();
if !deallocations.is_empty() {
for storage_id in deallocations.iter() {
let slice_id = self.pages[storage_id].slice_id;
self.pages.remove(storage_id);
self.slices.remove(&slice_id);
storage.dealloc(*storage_id);
}
self.index = 0;
self.ring_buffer
.retain(|storage| !deallocations.contains(storage));
}
}
}