use super::{
MemoryConfiguration, MemoryPoolOptions, MemoryUsage, PoolType,
memory_pool::{ExclusiveMemoryPool, MemoryPool, PersistentPool, SlicedPool},
};
use crate::{
config::{
GlobalConfig,
memory::{MemoryLogLevel, PersistentMemory},
},
logging::ServerLogger,
memory_management::{BytesFormat, memory_pool::Slice},
server::IoError,
storage::{ComputeStorage, StorageHandle},
};
use alloc::format;
use alloc::string::{String, ToString};
#[cfg(not(exclusive_memory_only))]
use alloc::vec;
use alloc::vec::Vec;
use cubecl_common::{backtrace::BackTrace, stub::Arc};
use cubecl_ir::MemoryDeviceProperties;
pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
#[allow(clippy::large_enum_variant)]
enum DynamicPool {
Sliced(SlicedPool),
Exclusive(ExclusiveMemoryPool),
}
impl MemoryPool for DynamicPool {
fn accept(&self, size: u64) -> bool {
match self {
DynamicPool::Sliced(pool) => pool.accept(size),
DynamicPool::Exclusive(pool) => pool.accept(size),
}
}
fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
match self {
DynamicPool::Sliced(m) => m.find(binding),
DynamicPool::Exclusive(m) => m.find(binding),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
match self {
DynamicPool::Sliced(m) => m.try_reserve(size),
DynamicPool::Exclusive(m) => m.try_reserve(size),
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip(self, storage))
)]
fn alloc<Storage: ComputeStorage>(
&mut self,
storage: &mut Storage,
size: u64,
) -> Result<ManagedMemoryHandle, IoError> {
match self {
DynamicPool::Sliced(m) => m.alloc(storage, size),
DynamicPool::Exclusive(m) => m.alloc(storage, size),
}
}
fn get_memory_usage(&self) -> MemoryUsage {
match self {
DynamicPool::Sliced(m) => m.get_memory_usage(),
DynamicPool::Exclusive(m) => m.get_memory_usage(),
}
}
fn cleanup<Storage: ComputeStorage>(
&mut self,
storage: &mut Storage,
alloc_nr: u64,
explicit: bool,
) {
match self {
DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
};
storage.flush();
}
fn bind(
&mut self,
reserved: ManagedMemoryHandle,
assigned: ManagedMemoryHandle,
cursor: u64,
) -> Result<(), IoError> {
match self {
DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
}
}
}
#[derive(Default, Clone, Copy, Debug)]
pub enum MemoryAllocationMode {
#[default]
Auto,
Persistent,
}
pub struct MemoryManagement<Storage> {
name: String,
persistent: PersistentPool,
pools: Vec<DynamicPool>,
storage: Storage,
alloc_reserve_count: u64,
mode: MemoryAllocationMode,
config: PersistentMemory,
logger: Arc<ServerLogger>,
}
fn generate_bucket_sizes(
start_size: u64,
end_size: u64,
max_buckets: usize,
alignment: u64,
) -> Vec<u64> {
let mut buckets = Vec::with_capacity(max_buckets);
let log_min = (start_size as f64).ln();
let log_max = (end_size as f64).ln();
let log_range = log_max - log_min;
for i in 0..max_buckets {
let p = i as f64 / (max_buckets - 1) as f64;
let log_size = log_min + log_range * p;
let size = log_size.exp() as u64;
let aligned_size = size.next_multiple_of(alignment);
buckets.push(aligned_size);
}
buckets.dedup();
buckets
}
const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
const BASE_DEALLOC_PERIOD: u64 = 5000;
#[derive(Debug)]
pub struct MemoryManagementOptions {
name: String,
memory: MemoryAllocationOption,
}
impl MemoryManagementOptions {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
memory: MemoryAllocationOption::FromConfig,
}
}
pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
self.memory = MemoryAllocationOption::Provided(mode);
self
}
}
#[derive(Default, Debug)]
enum MemoryAllocationOption {
#[default]
FromConfig,
Provided(MemoryAllocationMode),
}
impl<Storage: ComputeStorage> MemoryManagement<Storage> {
pub fn from_configuration(
storage: Storage,
properties: &MemoryDeviceProperties,
config: MemoryConfiguration,
logger: Arc<ServerLogger>,
options: MemoryManagementOptions,
) -> Self {
let pool_options = match config {
#[cfg(not(exclusive_memory_only))]
MemoryConfiguration::SubSlices => {
let memory_alignment = properties.alignment;
let max_page = properties.max_page_size;
let mut pools = Vec::new();
const MB: u64 = 1024 * 1024;
pools.push(MemoryPoolOptions {
pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
dealloc_period: None,
});
let mut current = max_page;
let mut max_sizes = vec![];
let mut page_sizes = vec![];
let mut base = pools.len() as u32;
while current >= 32 * MB {
current /= 4;
current = current.next_multiple_of(memory_alignment);
max_sizes.push(current / 2u64.pow(base));
page_sizes.push(current);
base += 1;
}
max_sizes.reverse();
page_sizes.reverse();
for i in 0..max_sizes.len() {
let max = max_sizes[i];
let page_size = page_sizes[i];
pools.push(MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size,
max_slice_size: max,
},
dealloc_period: None,
});
}
pools.push(MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size: max_page / memory_alignment * memory_alignment,
max_slice_size: max_page / memory_alignment * memory_alignment,
},
dealloc_period: None,
});
pools
}
MemoryConfiguration::ExclusivePages => {
const MIN_BUCKET_SIZE: u64 = 1024 * 32;
const NUM_POOLS: usize = 24;
let sizes = generate_bucket_sizes(
MIN_BUCKET_SIZE,
properties.max_page_size,
NUM_POOLS,
properties.alignment,
);
sizes
.iter()
.map(|&size| {
let dealloc_period = (BASE_DEALLOC_PERIOD as f64
* (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
as u64;
MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: size,
},
dealloc_period: Some(dealloc_period),
}
})
.collect()
}
MemoryConfiguration::Custom { pool_options } => pool_options,
};
logger.log_memory(
|level| !matches!(level, MemoryLogLevel::Disabled),
|| {
let mut msg = String::new();
for pool in pool_options.iter() {
msg += &format!("[{}] Using memory pool: \n {pool:?}\n", options.name);
}
msg
},
);
let pools: Vec<_> = pool_options
.iter()
.enumerate()
.map(|(pool_pos, options)| {
let pool_pos = pool_pos as u8;
match options.pool_type {
PoolType::SlicedPages {
page_size,
max_slice_size,
} => DynamicPool::Sliced(SlicedPool::new(
page_size,
max_slice_size,
properties.alignment,
pool_pos,
)),
PoolType::ExclusivePages { max_alloc_size } => {
DynamicPool::Exclusive(ExclusiveMemoryPool::new(
max_alloc_size,
properties.alignment,
options.dealloc_period.unwrap_or(u64::MAX),
pool_pos,
))
}
}
})
.collect();
let config = GlobalConfig::get().memory.persistent_memory.clone();
let mode = match options.memory {
MemoryAllocationOption::Provided(mode) => mode,
MemoryAllocationOption::FromConfig => match config {
PersistentMemory::Enabled => MemoryAllocationMode::Auto,
PersistentMemory::Disabled => MemoryAllocationMode::Auto,
PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
},
};
Self {
name: options.name,
persistent: PersistentPool::new(
properties.max_page_size,
properties.alignment,
pools.len() as u8,
),
pools,
storage,
alloc_reserve_count: 0,
mode,
config,
logger,
}
}
pub fn mode(&mut self, mode: MemoryAllocationMode) {
let mode = match self.config {
PersistentMemory::Enabled => mode,
PersistentMemory::Disabled | PersistentMemory::Enforced => return,
};
self.logger.log_memory(
|level| !matches!(level, MemoryLogLevel::Disabled),
|| {
format!(
"[{}] Setting memory allocation mode: from {:?} => {mode:?}",
self.name, self.mode
)
},
);
self.mode = mode;
}
pub fn cleanup(&mut self, explicit: bool) {
self.logger.log_memory(
|level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
|| "Manual memory cleanup ...".to_string(),
);
self.persistent
.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
for pool in self.pools.iter_mut() {
pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
}
}
pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
let slice = self.find(binding)?;
Ok(slice.cursor)
}
fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
let id = binding.descriptor();
if id.location().pool >= self.pools.len() as u8 {
return self.persistent.find(&binding);
}
let pool =
self.pools
.get(id.location().pool as usize)
.ok_or_else(|| IoError::NotFound {
backtrace: BackTrace::capture(),
reason: format!("Pool {} doesn't exist", id.location().pool).into(),
})?;
let slice = pool.find(&binding)?;
assert_eq!(slice.handle.descriptor(), binding.descriptor());
Ok(slice)
}
pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
let slice = self.find(binding)?;
Ok(slice.storage.clone())
}
pub fn get_resource(
&mut self,
binding: ManagedMemoryBinding,
offset_start: Option<u64>,
offset_end: Option<u64>,
) -> Result<Storage::Resource, IoError> {
let handle = self.get_storage(binding)?;
let handle = match offset_start {
Some(offset) => handle.offset_start(offset),
None => handle,
};
let handle = match offset_end {
Some(offset) => handle.offset_end(offset),
None => handle,
};
Ok(self.storage().get(&handle))
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
self.alloc_reserve_count += 1;
if let Some(val) = self.persistent.try_reserve(size) {
self.logger.log_memory(
|level| matches!(level, MemoryLogLevel::Full),
|| {
format!(
"[{}] Reserved memory {size} using persistent memory",
self.name
)
},
);
return Ok(val);
}
if matches!(self.mode, MemoryAllocationMode::Persistent) || self.persistent.has_size(size) {
let allocated = self.persistent.alloc(&mut self.storage, size);
self.logger.log_memory(
|level| !matches!(level, MemoryLogLevel::Disabled),
|| {
format!(
"[{}] Allocated a new memory page using persistent memory, \n{}",
self.name, self,
)
},
);
return allocated;
}
self.logger.log_memory(
|level| matches!(level, MemoryLogLevel::Full),
|| {
format!(
"[{}] Reserved memory {} using dynamic pool",
self.name,
BytesFormat::new(size)
)
},
);
let pool = self
.pools
.iter_mut()
.find(|p| p.accept(size))
.ok_or(IoError::BufferTooBig {
size,
backtrace: BackTrace::capture(),
})?;
if let Some(slice) = pool.try_reserve(size) {
return Ok(slice);
}
let allocated = pool.alloc(&mut self.storage, size);
self.logger.log_memory(
|level| matches!(level, MemoryLogLevel::Full),
|| {
format!(
"[{}], Allocated a new memory page, current usage: \n{}",
self.name, self
)
},
);
allocated
}
pub fn storage(&mut self) -> &mut Storage {
&mut self.storage
}
pub fn memory_usage(&self) -> MemoryUsage {
let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
MemoryUsage {
number_allocs: 0,
bytes_in_use: 0,
bytes_padding: 0,
bytes_reserved: 0,
},
|m1, m2| m1.combine(m2),
);
memory_usage.combine(self.persistent.get_memory_usage())
}
pub fn print_memory_usage(&self) {
#[cfg(feature = "std")]
log::info!("{}", self.memory_usage());
}
pub fn bind(
&mut self,
reserved: ManagedMemoryHandle,
assigned: ManagedMemoryHandle,
cursor: u64,
) -> Result<(), IoError> {
let descriptor = reserved.descriptor();
if descriptor.location().init == 0 {
return Err(IoError::NotFound {
backtrace: BackTrace::capture(),
reason: "Reserved memory isn't initialized".into(),
});
}
let pool_index = descriptor.location().pool as usize;
if pool_index >= self.pools.len() {
return self.persistent.bind(reserved, assigned, cursor);
}
self.pools
.get_mut(pool_index)
.map(|p| p.bind(reserved, assigned, cursor))
.ok_or_else(|| IoError::NotFound {
backtrace: BackTrace::capture(),
reason: format!("Memory pool {} doesn't exist", pool_index).into(),
})?
}
}
impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("\n# MemoryManagement\n\n")?;
f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
f.write_str("\n## Dynamic\n\n")?;
for pool in self.pools.iter() {
match pool {
DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
}
}
let memory_usage = self.memory_usage();
f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
Ok(())
}
}
impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(
alloc::format!(
"DynamicMemoryManagement {:?}",
core::any::type_name::<Storage>(),
)
.as_str(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{memory_management::MemoryManagement, storage::BytesStorage};
use alloc::vec;
const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 32,
};
fn options() -> MemoryManagementOptions {
MemoryManagementOptions {
name: "test".into(),
memory: MemoryAllocationOption::FromConfig,
}
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn test_handle_mutability() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::SubSlices,
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management.reserve(10).unwrap();
let other_ref = handle.clone();
assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
drop(other_ref);
assert!(handle.can_mut(), "Handle should be mut when only one ref.");
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn test_memory_usage() {
let max_page_size = 512;
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: max_page_size,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management.reserve(100);
let usage = memory_management.memory_usage();
assert_eq!(usage.bytes_in_use, 100);
assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
drop(handle);
let _handle = memory_management.reserve(100);
let usage_new = memory_management.memory_usage();
assert_eq!(usage, usage_new);
}
#[test_log::test]
fn alloc_two_chunks_on_one_page() {
let page_size = 2048;
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size,
max_slice_size: page_size,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 2);
assert_eq!(usage.bytes_in_use, alloc_size * 2);
assert_eq!(usage.bytes_reserved, page_size);
}
#[test_log::test]
fn alloc_reuses_storage() {
let page_size = 512;
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size,
max_slice_size: page_size,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle = memory_management.reserve(alloc_size);
drop(_handle);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 1);
assert_eq!(usage.bytes_in_use, alloc_size);
assert_eq!(usage.bytes_reserved, page_size);
}
#[test_log::test]
fn alloc_allocs_new_storage() {
let page_size = 1024;
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size,
max_slice_size: page_size,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 768;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 2);
assert_eq!(usage.bytes_in_use, alloc_size * 2);
assert_eq!(usage.bytes_reserved, page_size * 2);
}
#[test_log::test]
fn alloc_respects_alignment_size() {
let page_size = 500;
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: page_size,
alignment: 50,
},
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size,
max_slice_size: page_size,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 40;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.bytes_padding, 10 * 2);
}
#[test_log::test]
fn allocs_on_correct_page() {
let sizes = [100, 200, 300, 400];
let pools = sizes
.iter()
.map(|size| MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size: *size,
max_slice_size: *size,
},
dealloc_period: None,
})
.collect();
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 10,
},
MemoryConfiguration::Custom {
pool_options: pools,
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_sizes = [50, 150, 250, 350];
let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
let usage = memory_management.memory_usage();
assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn allocate_deallocate_reallocate() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 32,
},
MemoryConfiguration::SubSlices,
Arc::new(ServerLogger::default()),
options(),
);
let handles: Vec<_> = (0..5)
.map(|i| memory_management.reserve(1000 * (i + 1)))
.collect();
let usage_before = memory_management.memory_usage();
drop(handles);
let _new_handles: Vec<_> = (0..5)
.map(|i| memory_management.reserve(1000 * (i + 1)))
.collect();
let usage_after = memory_management.memory_usage();
assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn test_fragmentation_resistance() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 32,
},
MemoryConfiguration::SubSlices,
Arc::new(ServerLogger::default()),
options(),
);
let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
let handles: Vec<_> = sizes
.iter()
.map(|&size| memory_management.reserve(size).unwrap())
.collect();
let usage_before = memory_management.memory_usage();
for i in (0..handles.len()).step_by(2) {
drop(handles[i].clone());
}
for &size in &sizes[0..sizes.len() / 2] {
memory_management.reserve(size).unwrap();
}
let usage_after = memory_management.memory_usage();
assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
}
#[test_log::test]
fn noslice_test_handle_mutability() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&(MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 32,
}),
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management.reserve(10).unwrap();
let other_ref = handle.clone();
assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
drop(other_ref);
assert!(handle.can_mut(), "Handle should be mut when only one ref.");
}
#[test_log::test]
fn noslice_alloc_two_chunk() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: 1024,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 2);
assert_eq!(usage.bytes_in_use, alloc_size * 2);
assert!(usage.bytes_reserved >= alloc_size * 2);
}
#[test_log::test]
fn noslice_alloc_reuses_storage() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: 1024,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle = memory_management.reserve(alloc_size);
drop(_handle);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 1);
assert_eq!(usage.bytes_in_use, alloc_size);
assert!(usage.bytes_reserved >= alloc_size);
}
#[test_log::test]
fn noslice_alloc_allocs_new_storage() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: 1024,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 768;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.number_allocs, 2);
assert_eq!(usage.bytes_in_use, alloc_size * 2);
assert!(usage.bytes_reserved >= alloc_size * 2);
}
#[test_log::test]
fn noslice_alloc_respects_alignment_size() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: DUMMY_MEM_PROPS.max_page_size,
alignment: 50,
},
MemoryConfiguration::Custom {
pool_options: vec![MemoryPoolOptions {
pool_type: PoolType::ExclusivePages {
max_alloc_size: 50 * 20,
},
dealloc_period: None,
}],
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 40;
let _handle = memory_management.reserve(alloc_size);
let _new_handle = memory_management.reserve(alloc_size);
let usage = memory_management.memory_usage();
assert_eq!(usage.bytes_padding, 10 * 2);
}
#[test_log::test]
fn noslice_allocs_on_correct_page() {
let pools = [100, 200, 300, 400]
.iter()
.map(|&size| MemoryPoolOptions {
pool_type: PoolType::SlicedPages {
page_size: size,
max_slice_size: size,
},
dealloc_period: None,
})
.collect();
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: DUMMY_MEM_PROPS.max_page_size,
alignment: 10,
},
MemoryConfiguration::Custom {
pool_options: pools,
},
Arc::new(ServerLogger::default()),
options(),
);
let alloc_sizes = [50, 150, 250, 350];
let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
let usage = memory_management.memory_usage();
assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
}
#[test_log::test]
fn noslice_allocate_deallocate_reallocate() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties {
max_page_size: 128 * 1024 * 1024,
alignment: 32,
},
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let handles: Vec<_> = (0..5)
.map(|i| memory_management.reserve(1000 * (i + 1)))
.collect();
let usage_before = memory_management.memory_usage();
drop(handles);
let _new_handles: Vec<_> = (0..5)
.map(|i| memory_management.reserve(1000 * (i + 1)))
.collect();
let usage_after = memory_management.memory_usage();
assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
}
}