use super::{
DEDICATED_POOL_POS, ManagedMemoryBinding, ManagedMemoryDescriptor, ManagedMemoryHandle,
MemoryAllocationMode, MemoryConfiguration, MemoryLocation, MemoryPoolsReport,
PERSISTENT_POOL_POS, PageGuard,
memory_pool::{DirectPool, MemoryPool, PageMapping, PersistentPool},
};
use crate::{
config::{
CubeClRuntimeConfig, RuntimeConfig,
memory::{MemoryLogLevel, PersistentMemory},
},
logging::ServerLogger,
memory_management::{BytesFormat, DynamicMemory, ErrorGraph, FailureId, memory_pool::Slice},
server::IoError,
storage::{ComputeStorage, ManagedResource, StorageHandle},
};
use crate::memory_management::relocation::{CopyQueue, RelocationNeed, RelocationReason};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::ops::Range;
use cubecl_environment::backtrace::BackTrace;
use cubecl_environment::sync::Arc;
use cubecl_ir::MemoryDeviceProperties;
#[derive(Clone, Copy)]
enum PoolPosition {
Persistent,
Dedicated,
Dynamic(u8),
}
impl PoolPosition {
fn new(pool: u8) -> Self {
match pool {
PERSISTENT_POOL_POS => PoolPosition::Persistent,
DEDICATED_POOL_POS => PoolPosition::Dedicated,
index => PoolPosition::Dynamic(index),
}
}
fn missing(index: u8) -> IoError {
IoError::NotFound {
backtrace: BackTrace::capture(),
reason: format!("Memory pool {index} doesn't exist").into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PageUpdate {
Allow,
AddOnly,
Forbidden,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cleanup {
Periodic,
Explicit,
}
pub struct MemoryManagement<Storage> {
name: String,
persistent: PersistentPool,
dedicated: DirectPool,
pools: DynamicMemory,
storage: Storage,
alloc_reserve_count: u64,
mode: MemoryAllocationMode,
base_mode: MemoryAllocationMode,
windows: Vec<MemoryAllocationMode>,
config: PersistentMemory,
logger: Arc<ServerLogger>,
}
#[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 pools = DynamicMemory::new(properties, config, logger.clone(), options.name.clone());
let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
let mode = match options.memory {
MemoryAllocationOption::Provided(mode) => mode,
MemoryAllocationOption::FromConfig => match config {
PersistentMemory::Enabled | PersistentMemory::SizeMatch => {
MemoryAllocationMode::Auto
}
PersistentMemory::Disabled => MemoryAllocationMode::Auto,
PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
},
};
Self {
name: options.name,
persistent: PersistentPool::new(
properties.max_page_size,
properties.alignment,
PERSISTENT_POOL_POS,
),
dedicated: DirectPool::new(properties.alignment, DEDICATED_POOL_POS, Some(0)),
pools,
storage,
alloc_reserve_count: 0,
mode,
base_mode: mode,
windows: Vec::new(),
config,
logger,
}
}
pub fn mode(&mut self, mode: MemoryAllocationMode) {
match mode {
MemoryAllocationMode::Auto => {
self.windows.pop();
}
MemoryAllocationMode::Persistent => self.windows.push(match self.config {
PersistentMemory::Enabled | PersistentMemory::SizeMatch => mode,
PersistentMemory::Disabled | PersistentMemory::Enforced => self.base_mode,
}),
MemoryAllocationMode::Dedicated => self.windows.push(mode),
}
let mode = self.windows.last().copied().unwrap_or(self.base_mode);
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, cleanup: Cleanup, failures: &mut ErrorGraph) {
self.logger.log_memory(
|level| !matches!(level, MemoryLogLevel::Disabled) && cleanup == Cleanup::Explicit,
|| "Manual memory cleanup ...".to_string(),
);
self.persistent.cleanup(
&mut self.storage,
self.alloc_reserve_count,
cleanup,
failures,
);
if self.dedicated.reclaim(&mut self.storage, failures) {
self.storage.flush();
}
self.pools.cleanup(
&mut self.storage,
self.alloc_reserve_count,
cleanup,
failures,
);
if cleanup == Cleanup::Explicit {
self.storage.flush();
}
}
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 slice = self.pool(allocated(binding.descriptor())?)?.find(binding)?;
owned_by(slice, binding)?;
Ok(slice)
}
fn find_mut(&mut self, binding: &ManagedMemoryBinding) -> Result<&mut Slice, IoError> {
let slice = self
.pool_mut(allocated(binding.descriptor())?)?
.find_mut(binding)?;
owned_by(slice, binding)?;
Ok(slice)
}
fn pool(&self, location: MemoryLocation) -> Result<&dyn MemoryPool, IoError> {
match PoolPosition::new(location.pool) {
PoolPosition::Persistent => Ok(&self.persistent),
PoolPosition::Dedicated => Ok(&self.dedicated),
PoolPosition::Dynamic(index) => self
.pools
.pool(index)
.ok_or_else(|| PoolPosition::missing(index)),
}
}
fn pool_mut(&mut self, location: MemoryLocation) -> Result<&mut dyn MemoryPool, IoError> {
match PoolPosition::new(location.pool) {
PoolPosition::Persistent => Ok(&mut self.persistent),
PoolPosition::Dedicated => Ok(&mut self.dedicated),
PoolPosition::Dynamic(index) => self
.pools
.pool_mut(index)
.ok_or_else(|| PoolPosition::missing(index)),
}
}
pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
self.materialize(&binding)?;
let slice = self.find(&binding)?;
Ok(slice.storage.clone())
}
pub fn bytes_allocated(&self) -> u64 {
self.storage.bytes_allocated()
}
pub fn relocation_need(&self) -> RelocationNeed {
self.pools.relocation_need()
}
pub fn has_outdated(&self) -> bool {
self.pools.has_outdated()
}
pub fn relocate(
&mut self,
copier: &mut dyn CopyQueue<Storage>,
reason: RelocationReason,
failures: &mut ErrorGraph,
) {
self.pools
.relocate(&mut self.storage, copier, reason, failures);
}
pub fn guard(&mut self, location: MemoryLocation) -> Option<PageGuard> {
self.pool_mut(location).ok()?.guard(location)
}
fn materialize(&mut self, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
let location = binding.descriptor().location();
if location.init == 0 {
return Ok(());
}
match PoolPosition::new(location.pool) {
PoolPosition::Persistent => self.persistent.materialize(&mut self.storage, binding),
PoolPosition::Dedicated => self.dedicated.materialize(&mut self.storage, binding),
PoolPosition::Dynamic(_) => self.pools.materialize(&mut self.storage, binding),
}
}
pub fn managed_resource(
&mut self,
binding: ManagedMemoryBinding,
offset_start: Option<u64>,
offset_end: Option<u64>,
) -> Result<ManagedResource<Storage::Resource>, IoError> {
let resource = self.get_resource(binding.clone(), offset_start, offset_end)?;
Ok(ManagedResource::new(binding, resource))
}
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,
};
self.storage().get(&handle)
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
pub fn reserve(
&mut self,
size: u64,
update: PageUpdate,
failures: &mut ErrorGraph,
) -> Result<ManagedMemoryHandle, IoError> {
match update {
PageUpdate::Allow => {
self.cleanup(Cleanup::Periodic, failures);
self.reserve_adding(size, failures)
}
PageUpdate::AddOnly => self.reserve_adding(size, failures),
PageUpdate::Forbidden => {
self.reserve_held(size, failures)
.ok_or_else(|| IoError::PageUpdateForbidden {
size,
backtrace: BackTrace::capture(),
})
}
}
}
fn reserve_adding(
&mut self,
size: u64,
failures: &mut ErrorGraph,
) -> Result<ManagedMemoryHandle, IoError> {
self.alloc_reserve_count += 1;
let mapping = PageMapping::current();
if matches!(self.mode, MemoryAllocationMode::Dedicated) {
return self
.dedicated
.alloc(&mut self.storage, size, mapping, failures);
}
let persistent_mode = matches!(self.mode, MemoryAllocationMode::Persistent);
let size_match = matches!(self.config, PersistentMemory::SizeMatch);
if (persistent_mode || size_match)
&& let Some(val) = self.persistent.try_reserve(size, failures)
{
self.logger.log_memory(
|level| matches!(level, MemoryLogLevel::Full),
|| {
format!(
"[{}] Reserved memory {size} using persistent memory",
self.name
)
},
);
return Ok(val);
}
if persistent_mode || (size_match && self.persistent.has_size(size)) {
let allocated = self
.persistent
.alloc(&mut self.storage, size, mapping, failures);
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 reserved = self
.pools
.reserve(&mut self.storage, size, mapping, failures)?;
self.logger.log_memory(
|level| matches!(level, MemoryLogLevel::Full),
|| {
format!(
"[{}], Allocated a new memory page, current usage: \n{}",
self.name, self
)
},
);
Ok(reserved)
}
fn reserve_held(
&mut self,
size: u64,
failures: &mut ErrorGraph,
) -> Option<ManagedMemoryHandle> {
match self.mode {
MemoryAllocationMode::Dedicated => self.dedicated.try_reserve(size, failures),
MemoryAllocationMode::Persistent => self.persistent.try_reserve(size, failures),
MemoryAllocationMode::Auto => {
if matches!(self.config, PersistentMemory::SizeMatch)
&& let Some(handle) = self.persistent.try_reserve(size, failures)
{
return Some(handle);
}
self.pools.try_reserve(size, failures)
}
}
}
pub fn storage(&mut self) -> &mut Storage {
&mut self.storage
}
pub fn memory_report(&self) -> MemoryPoolsReport {
MemoryPoolsReport {
dynamic: self.pools.report(),
persistent: self.persistent.report(),
dedicated: self.dedicated.report(),
}
}
pub fn print_memory_usage(&self) {
#[cfg(feature = "std")]
log::info!("{}", self.memory_report().usage());
}
pub fn bind(
&mut self,
reserved: ManagedMemoryHandle,
assigned: ManagedMemoryHandle,
cursor: u64,
failures: &mut ErrorGraph,
) -> Result<(), IoError> {
let location = allocated(reserved.descriptor())?;
self.pool_mut(location)?
.bind(reserved, assigned, cursor, failures)
}
pub fn failure(&self, binding: &ManagedMemoryBinding, range: Range<u64>) -> Option<FailureId> {
self.find(binding)
.ok()
.and_then(|slice| slice.tainted.failure(&range))
}
pub fn taint(
&mut self,
binding: &ManagedMemoryBinding,
range: Range<u64>,
failure: FailureId,
failures: &mut ErrorGraph,
) {
if let Ok(slice) = self.find_mut(binding) {
slice.tainted.taint(range, failure, failures);
}
}
pub fn written(
&mut self,
binding: &ManagedMemoryBinding,
range: Range<u64>,
failures: &mut ErrorGraph,
) {
if let Ok(slice) = self.find_mut(binding) {
slice.tainted.written(range, failures);
}
}
}
fn allocated(descriptor: &ManagedMemoryDescriptor) -> Result<MemoryLocation, IoError> {
if !descriptor.is_allocated() {
return Err(IoError::NotFound {
backtrace: BackTrace::capture(),
reason: "Memory location was never initialized".into(),
});
}
Ok(descriptor.location())
}
fn owned_by(slice: &Slice, binding: &ManagedMemoryBinding) -> Result<(), IoError> {
if slice.handle.descriptor() != binding.descriptor() {
return Err(IoError::NotFound {
backtrace: BackTrace::capture(),
reason: "Memory location points to a different allocation".into(),
});
}
Ok(())
}
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")?;
f.write_fmt(format_args!("{}", self.pools))?;
let memory_usage = self.memory_report().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::*;
#[cfg(not(exclusive_memory_only))]
use crate::memory_management::MemoryPoolKind;
use crate::{memory_management::MemoryManagement, storage::BytesStorage};
const MIB: u64 = 1024 * 1024;
const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties::new(128 * MIB, 32);
#[cfg(not(exclusive_memory_only))]
const METADATA_PAGE: u64 = 8 * MIB;
fn options() -> MemoryManagementOptions {
MemoryManagementOptions {
name: "test".into(),
memory: MemoryAllocationOption::FromConfig,
}
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn adaptive_preset_routes_near_page_sizes_to_the_adaptive_pool() {
let mut memory = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let _near = memory
.reserve(7 * MIB, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let served = memory
.memory_report()
.dynamic
.into_iter()
.find(|pool| pool.largest_alloc == 7 * MIB)
.expect("some pool served the allocation");
assert!(
matches!(served.kind, MemoryPoolKind::Adaptive { .. }),
"served by {:?}",
served.kind
);
}
#[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::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management
.reserve(10, PageUpdate::Allow, &mut ErrorGraph::default())
.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 mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management.reserve(100, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().usage();
assert_eq!(usage.bytes_in_use, 100);
assert_eq!(usage.bytes_reserved, METADATA_PAGE);
drop(handle);
let _handle = memory_management.reserve(100, PageUpdate::Allow, &mut ErrorGraph::default());
let usage_new = memory_management.memory_report().usage();
assert_eq!(usage, usage_new);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn find_uninit_binding_returns_not_found() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let _live = memory_management
.reserve(512, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let binding = ManagedMemoryHandle::new().binding();
assert!(matches!(
memory_management.get_cursor(binding),
Err(IoError::NotFound { .. })
));
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn find_stale_descriptor_returns_not_found() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let reserved = memory_management
.reserve(512, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let stale = reserved.clone();
let assigned = ManagedMemoryHandle::new();
let assigned_binding = assigned.clone().binding();
memory_management
.bind(reserved, assigned, 0, &mut ErrorGraph::default())
.unwrap();
assert!(matches!(
memory_management.get_cursor(stale.binding()),
Err(IoError::NotFound { .. })
));
assert!(memory_management.get_cursor(assigned_binding).is_ok());
}
#[test_log::test]
fn held_binding_survives_explicit_cleanup_renumber() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let handle_a = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let handle_b = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let handle_c = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let binding_b = handle_b.binding();
let reserved = memory_management.memory_report().usage().bytes_reserved;
drop(handle_a);
drop(handle_c);
memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
assert!(memory_management.get_cursor(binding_b.clone()).is_ok());
assert!(memory_management.get_storage(binding_b).is_ok());
assert!(
memory_management.memory_report().usage().bytes_reserved < reserved,
"the two freed pages must have gone back"
);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn persistent_windows_nest() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Persistent); memory_management.mode(MemoryAllocationMode::Auto);
let weight = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let report = memory_management.memory_report();
assert_eq!(
report.persistent.usage.bytes_in_use, 1024,
"an allocation inside the outer window is persistent"
);
assert_eq!(
report
.dynamic
.iter()
.map(|pool| pool.pages_peak)
.sum::<u64>(),
0,
"nothing leaked into the dynamic pools"
);
memory_management.mode(MemoryAllocationMode::Auto);
let _transient = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
assert_eq!(
memory_management
.memory_report()
.dynamic
.iter()
.map(|pool| pool.pages_peak)
.sum::<u64>(),
1
);
drop(weight);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn alloc_two_chunks_on_one_page() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().usage();
assert_eq!(usage.number_allocs, 2);
assert_eq!(usage.bytes_in_use, alloc_size * 2);
assert_eq!(usage.bytes_reserved, METADATA_PAGE);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn alloc_reuses_storage() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
drop(_handle);
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().usage();
assert_eq!(usage.number_allocs, 1);
assert_eq!(usage.bytes_in_use, alloc_size);
assert_eq!(usage.bytes_reserved, METADATA_PAGE);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn alloc_allocs_new_storage() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let page_size = 2 * MIB;
let alloc_size = page_size / 4 * 3;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().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]
#[cfg(not(exclusive_memory_only))]
fn alloc_respects_alignment_size() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 50),
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 40;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().usage();
assert_eq!(usage.bytes_padding, 10 * 2);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn allocs_on_correct_page() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_sizes = [4096, 4 * MIB];
let handles = alloc_sizes.map(|size| {
memory_management
.reserve(size, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap()
});
assert_ne!(
handles[0].descriptor().location().pool,
handles[1].descriptor().location().pool,
"metadata and the workload must not share a pool"
);
let usage = memory_management.memory_report().usage();
assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
assert_eq!(usage.bytes_reserved, METADATA_PAGE + 5 * MIB);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn single_arena_reuses_across_sizes() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let small = memory_management
.reserve(128 * 1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
drop(small);
let large = memory_management
.reserve(512 * 1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let usage = memory_management.memory_report().usage();
assert_eq!(
usage.bytes_reserved,
2 * MIB,
"both sizes must share a single arena page"
);
assert_eq!(usage.number_allocs, 1);
drop(large);
}
#[test_log::test]
#[cfg(not(exclusive_memory_only))]
fn allocate_deallocate_reallocate() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
Arc::new(ServerLogger::default()),
options(),
);
let handles: Vec<_> = (0..5)
.map(|i| {
memory_management.reserve(
1000 * (i + 1),
PageUpdate::Allow,
&mut ErrorGraph::default(),
)
})
.collect();
let usage_before = memory_management.memory_report().usage();
drop(handles);
let _new_handles: Vec<_> = (0..5)
.map(|i| {
memory_management.reserve(
1000 * (i + 1),
PageUpdate::Allow,
&mut ErrorGraph::default(),
)
})
.collect();
let usage_after = memory_management.memory_report().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(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::Adaptive,
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, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap()
})
.collect();
let usage_before = memory_management.memory_report().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, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
}
let usage_after = memory_management.memory_report().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(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let handle = memory_management
.reserve(10, PageUpdate::Allow, &mut ErrorGraph::default())
.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::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().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::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 512;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
drop(_handle);
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().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::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 768;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().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::new(DUMMY_MEM_PROPS.max_page_size, 50),
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_size = 40;
let _handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let _new_handle =
memory_management.reserve(alloc_size, PageUpdate::Allow, &mut ErrorGraph::default());
let usage = memory_management.memory_report().usage();
assert_eq!(usage.bytes_padding, 10 * 2);
}
#[test_log::test]
fn noslice_allocs_on_correct_page() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&MemoryDeviceProperties::new(DUMMY_MEM_PROPS.max_page_size, 10),
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let alloc_sizes = [50, 150, 250, 350];
let _handles = alloc_sizes
.map(|s| memory_management.reserve(s, PageUpdate::Allow, &mut ErrorGraph::default()));
let usage = memory_management.memory_report().usage();
assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
}
#[test_log::test]
fn a_guarded_allocation_is_never_handed_out_again() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let recorded = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let guard = memory_management
.guard(recorded.descriptor().location())
.expect("the pool holds the page");
drop(recorded);
let before = memory_management.memory_report().usage().bytes_reserved;
let other = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let after = memory_management.memory_report().usage().bytes_reserved;
assert!(after > before, "the guarded page was handed out again");
memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
assert_eq!(
memory_management.memory_report().usage().bytes_reserved,
after,
"the guarded page was released"
);
drop(guard);
drop(other);
memory_management.cleanup(Cleanup::Explicit, &mut ErrorGraph::default());
assert_eq!(memory_management.memory_report().usage().bytes_reserved, 0);
}
#[test_log::test]
fn a_guard_leaves_in_place_updates_alone() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let cache = memory_management
.reserve(1024, PageUpdate::Allow, &mut ErrorGraph::default())
.unwrap();
let _guard = memory_management.guard(cache.descriptor().location());
assert!(
cache.can_mut(),
"a graph replaying against a buffer must not stop it being updated in place"
);
}
#[test_log::test]
fn noslice_allocate_deallocate_reallocate() {
let mut memory_management = MemoryManagement::from_configuration(
BytesStorage::default(),
&DUMMY_MEM_PROPS,
MemoryConfiguration::ExclusivePages,
Arc::new(ServerLogger::default()),
options(),
);
let handles: Vec<_> = (0..5)
.map(|i| {
memory_management.reserve(
1000 * (i + 1),
PageUpdate::Allow,
&mut ErrorGraph::default(),
)
})
.collect();
let usage_before = memory_management.memory_report().usage();
drop(handles);
let _new_handles: Vec<_> = (0..5)
.map(|i| {
memory_management.reserve(
1000 * (i + 1),
PageUpdate::Allow,
&mut ErrorGraph::default(),
)
})
.collect();
let usage_after = memory_management.memory_report().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);
}
}