1use super::{
2 MemoryConfiguration, MemoryPoolOptions, MemoryUsage, PoolType,
3 memory_pool::{ExclusiveMemoryPool, MemoryPool, PersistentPool, SlicedPool},
4};
5use crate::{
6 config::{
7 CubeClRuntimeConfig, RuntimeConfig,
8 memory::{MemoryLogLevel, PersistentMemory},
9 },
10 logging::ServerLogger,
11 memory_management::{BytesFormat, memory_pool::Slice},
12 server::IoError,
13 storage::{ComputeStorage, StorageHandle},
14};
15
16use alloc::format;
17use alloc::string::{String, ToString};
18#[cfg(not(exclusive_memory_only))]
19use alloc::vec;
20use alloc::vec::Vec;
21use cubecl_common::{backtrace::BackTrace, stub::Arc};
22use cubecl_ir::MemoryDeviceProperties;
23
24pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
25
26#[allow(clippy::large_enum_variant)]
29enum DynamicPool {
30 Sliced(SlicedPool),
31 Exclusive(ExclusiveMemoryPool),
32}
33
34impl MemoryPool for DynamicPool {
35 fn accept(&self, size: u64) -> bool {
36 match self {
37 DynamicPool::Sliced(pool) => pool.accept(size),
38 DynamicPool::Exclusive(pool) => pool.accept(size),
39 }
40 }
41
42 fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
43 match self {
44 DynamicPool::Sliced(m) => m.find(binding),
45 DynamicPool::Exclusive(m) => m.find(binding),
46 }
47 }
48
49 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
50 fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
51 match self {
52 DynamicPool::Sliced(m) => m.try_reserve(size),
53 DynamicPool::Exclusive(m) => m.try_reserve(size),
54 }
55 }
56
57 #[cfg_attr(
58 feature = "tracing",
59 tracing::instrument(level = "trace", skip(self, storage))
60 )]
61 fn alloc<Storage: ComputeStorage>(
62 &mut self,
63 storage: &mut Storage,
64 size: u64,
65 ) -> Result<ManagedMemoryHandle, IoError> {
66 match self {
67 DynamicPool::Sliced(m) => m.alloc(storage, size),
68 DynamicPool::Exclusive(m) => m.alloc(storage, size),
69 }
70 }
71
72 fn get_memory_usage(&self) -> MemoryUsage {
73 match self {
74 DynamicPool::Sliced(m) => m.get_memory_usage(),
75 DynamicPool::Exclusive(m) => m.get_memory_usage(),
76 }
77 }
78
79 fn cleanup<Storage: ComputeStorage>(
80 &mut self,
81 storage: &mut Storage,
82 alloc_nr: u64,
83 explicit: bool,
84 ) {
85 match self {
86 DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
87 DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
88 };
89 storage.flush();
90 }
91
92 fn bind(
93 &mut self,
94 reserved: ManagedMemoryHandle,
95 assigned: ManagedMemoryHandle,
96 cursor: u64,
97 ) -> Result<(), IoError> {
98 match self {
99 DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
100 DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
101 }
102 }
103}
104
105#[derive(Default, Clone, Copy, Debug)]
106pub enum MemoryAllocationMode {
108 #[default]
110 Auto,
111 Persistent,
114}
115
116pub struct MemoryManagement<Storage> {
118 name: String,
119 persistent: PersistentPool,
120 pools: Vec<DynamicPool>,
121 storage: Storage,
122 alloc_reserve_count: u64,
123 mode: MemoryAllocationMode,
124 config: PersistentMemory,
125 logger: Arc<ServerLogger>,
126}
127
128fn generate_bucket_sizes(
129 start_size: u64,
130 end_size: u64,
131 max_buckets: usize,
132 alignment: u64,
133) -> Vec<u64> {
134 let mut buckets = Vec::with_capacity(max_buckets);
135 let log_min = (start_size as f64).ln();
136 let log_max = (end_size as f64).ln();
137 let log_range = log_max - log_min;
138
139 for i in 0..max_buckets {
141 let p = i as f64 / (max_buckets - 1) as f64;
142 let log_size = log_min + log_range * p;
144 let size = log_size.exp() as u64;
145 let aligned_size = size.next_multiple_of(alignment);
146 buckets.push(aligned_size);
147 }
148
149 buckets.dedup();
150 buckets
151}
152
153const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
154const BASE_DEALLOC_PERIOD: u64 = 5000;
155
156#[derive(Debug)]
158pub struct MemoryManagementOptions {
159 name: String,
161 memory: MemoryAllocationOption,
163}
164
165impl MemoryManagementOptions {
166 pub fn new<S: Into<String>>(name: S) -> Self {
168 Self {
169 name: name.into(),
170 memory: MemoryAllocationOption::FromConfig,
171 }
172 }
173
174 pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
176 self.memory = MemoryAllocationOption::Provided(mode);
177 self
178 }
179}
180
181#[derive(Default, Debug)]
182enum MemoryAllocationOption {
184 #[default]
185 FromConfig,
187 Provided(MemoryAllocationMode),
189}
190
191impl<Storage: ComputeStorage> MemoryManagement<Storage> {
192 pub fn from_configuration(
194 storage: Storage,
195 properties: &MemoryDeviceProperties,
196 config: MemoryConfiguration,
197 logger: Arc<ServerLogger>,
198 options: MemoryManagementOptions,
199 ) -> Self {
200 let pool_options = match config {
201 #[cfg(not(exclusive_memory_only))]
202 MemoryConfiguration::SubSlices => {
203 let memory_alignment = properties.alignment;
205 let max_page = properties.max_page_size;
206 let mut pools = Vec::new();
207
208 const MB: u64 = 1024 * 1024;
209
210 pools.push(MemoryPoolOptions {
213 pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
214 dealloc_period: None,
215 });
216
217 let mut current = max_page;
218 let mut max_sizes = vec![];
219 let mut page_sizes = vec![];
220 let mut base = pools.len() as u32;
221
222 while current >= 32 * MB {
223 current /= 4;
224
225 current = current.next_multiple_of(memory_alignment);
227
228 max_sizes.push(current / 2u64.pow(base));
229 page_sizes.push(current);
230 base += 1;
231 }
232
233 max_sizes.reverse();
234 page_sizes.reverse();
235
236 for i in 0..max_sizes.len() {
237 let max = max_sizes[i];
238 let page_size = page_sizes[i];
239
240 pools.push(MemoryPoolOptions {
241 pool_type: PoolType::SlicedPages {
243 page_size,
244 max_slice_size: max,
245 },
246 dealloc_period: None,
247 });
248 }
249
250 pools.push(MemoryPoolOptions {
252 pool_type: PoolType::ExclusivePages {
255 max_alloc_size: max_page / memory_alignment * memory_alignment,
256 },
257 dealloc_period: None,
258 });
259 pools
260 }
261 MemoryConfiguration::ExclusivePages => {
262 const MIN_BUCKET_SIZE: u64 = 1024 * 32;
266 const NUM_POOLS: usize = 24;
267
268 let sizes = generate_bucket_sizes(
269 MIN_BUCKET_SIZE,
270 properties.max_page_size,
271 NUM_POOLS,
272 properties.alignment,
273 );
274
275 sizes
276 .iter()
277 .map(|&size| {
278 let dealloc_period = (BASE_DEALLOC_PERIOD as f64
279 * (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
280 as u64;
281
282 MemoryPoolOptions {
283 pool_type: PoolType::ExclusivePages {
284 max_alloc_size: size,
285 },
286 dealloc_period: Some(dealloc_period),
287 }
288 })
289 .collect()
290 }
291 MemoryConfiguration::Custom { pool_options } => pool_options,
292 };
293
294 logger.log_memory(
295 |level| !matches!(level, MemoryLogLevel::Disabled),
296 || {
297 let mut msg = String::new();
298 for pool in pool_options.iter() {
299 msg += &format!("[{}] Using memory pool: \n {pool:?}\n", options.name);
300 }
301 msg
302 },
303 );
304
305 let pools: Vec<_> = pool_options
306 .iter()
307 .enumerate()
308 .map(|(pool_pos, options)| {
309 let pool_pos = pool_pos as u8;
310
311 match options.pool_type {
312 PoolType::SlicedPages {
313 page_size,
314 max_slice_size,
315 } => DynamicPool::Sliced(SlicedPool::new(
316 page_size,
317 max_slice_size,
318 properties.alignment,
319 pool_pos,
320 )),
321 PoolType::ExclusivePages { max_alloc_size } => {
322 DynamicPool::Exclusive(ExclusiveMemoryPool::new(
323 max_alloc_size,
324 properties.alignment,
325 options.dealloc_period.unwrap_or(u64::MAX),
326 pool_pos,
327 ))
328 }
329 }
330 })
331 .collect();
332
333 let config = CubeClRuntimeConfig::get().memory.persistent_memory.clone();
334
335 let mode = match options.memory {
336 MemoryAllocationOption::Provided(mode) => mode,
337 MemoryAllocationOption::FromConfig => match config {
338 PersistentMemory::Enabled => MemoryAllocationMode::Auto,
339 PersistentMemory::Disabled => MemoryAllocationMode::Auto,
340 PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
341 },
342 };
343
344 Self {
345 name: options.name,
346 persistent: PersistentPool::new(
347 properties.max_page_size,
348 properties.alignment,
349 pools.len() as u8,
350 ),
351 pools,
352 storage,
353 alloc_reserve_count: 0,
354 mode,
355 config,
356 logger,
357 }
358 }
359
360 pub fn mode(&mut self, mode: MemoryAllocationMode) {
362 let mode = match self.config {
364 PersistentMemory::Enabled => mode,
365 PersistentMemory::Disabled | PersistentMemory::Enforced => return,
366 };
367
368 self.logger.log_memory(
369 |level| !matches!(level, MemoryLogLevel::Disabled),
370 || {
371 format!(
372 "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
373 self.name, self.mode
374 )
375 },
376 );
377 self.mode = mode;
378 }
379
380 pub fn cleanup(&mut self, explicit: bool) {
382 self.logger.log_memory(
383 |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
384 || "Manual memory cleanup ...".to_string(),
385 );
386
387 self.persistent
388 .cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
389
390 for pool in self.pools.iter_mut() {
391 pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
392 }
393 }
394
395 pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
397 let slice = self.find(binding)?;
398 Ok(slice.cursor)
399 }
400
401 fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
403 let id = binding.descriptor();
404
405 if id.location().pool >= self.pools.len() as u8 {
406 return self.persistent.find(&binding);
407 }
408
409 let pool =
410 self.pools
411 .get(id.location().pool as usize)
412 .ok_or_else(|| IoError::NotFound {
413 backtrace: BackTrace::capture(),
414 reason: format!("Pool {} doesn't exist", id.location().pool).into(),
415 })?;
416
417 let slice = pool.find(&binding)?;
418
419 assert_eq!(slice.handle.descriptor(), binding.descriptor());
420
421 Ok(slice)
422 }
423
424 pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
426 let slice = self.find(binding)?;
427 Ok(slice.storage.clone())
428 }
429
430 pub fn get_resource(
432 &mut self,
433 binding: ManagedMemoryBinding,
434 offset_start: Option<u64>,
435 offset_end: Option<u64>,
436 ) -> Result<Storage::Resource, IoError> {
437 let handle = self.get_storage(binding)?;
438
439 let handle = match offset_start {
440 Some(offset) => handle.offset_start(offset),
441 None => handle,
442 };
443 let handle = match offset_end {
444 Some(offset) => handle.offset_end(offset),
445 None => handle,
446 };
447 Ok(self.storage().get(&handle))
448 }
449
450 #[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(self)))]
452 pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
453 self.alloc_reserve_count += 1;
456
457 if let Some(val) = self.persistent.try_reserve(size) {
458 self.logger.log_memory(
459 |level| matches!(level, MemoryLogLevel::Full),
460 || {
461 format!(
462 "[{}] Reserved memory {size} using persistent memory",
463 self.name
464 )
465 },
466 );
467 return Ok(val);
468 }
469
470 if matches!(self.mode, MemoryAllocationMode::Persistent) || self.persistent.has_size(size) {
471 let allocated = self.persistent.alloc(&mut self.storage, size);
472
473 self.logger.log_memory(
474 |level| !matches!(level, MemoryLogLevel::Disabled),
475 || {
476 format!(
477 "[{}] Allocated a new memory page using persistent memory, \n{}",
478 self.name, self,
479 )
480 },
481 );
482 return allocated;
483 }
484
485 self.logger.log_memory(
486 |level| matches!(level, MemoryLogLevel::Full),
487 || {
488 format!(
489 "[{}] Reserved memory {} using dynamic pool",
490 self.name,
491 BytesFormat::new(size)
492 )
493 },
494 );
495
496 let pool = self
498 .pools
499 .iter_mut()
500 .find(|p| p.accept(size))
501 .ok_or(IoError::BufferTooBig {
502 size,
503 backtrace: BackTrace::capture(),
504 })?;
505
506 if let Some(slice) = pool.try_reserve(size) {
507 return Ok(slice);
508 }
509
510 let allocated = pool.alloc(&mut self.storage, size);
511
512 self.logger.log_memory(
513 |level| matches!(level, MemoryLogLevel::Full),
514 || {
515 format!(
516 "[{}], Allocated a new memory page, current usage: \n{}",
517 self.name, self
518 )
519 },
520 );
521
522 allocated
523 }
524
525 pub fn storage(&mut self) -> &mut Storage {
536 &mut self.storage
537 }
538
539 pub fn memory_usage(&self) -> MemoryUsage {
541 let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
542 MemoryUsage {
543 number_allocs: 0,
544 bytes_in_use: 0,
545 bytes_padding: 0,
546 bytes_reserved: 0,
547 },
548 |m1, m2| m1.combine(m2),
549 );
550 memory_usage.combine(self.persistent.get_memory_usage())
551 }
552
553 pub fn print_memory_usage(&self) {
555 #[cfg(feature = "std")]
556 log::info!("{}", self.memory_usage());
557 }
558
559 pub fn bind(
561 &mut self,
562 reserved: ManagedMemoryHandle,
563 assigned: ManagedMemoryHandle,
564 cursor: u64,
565 ) -> Result<(), IoError> {
566 let descriptor = reserved.descriptor();
567
568 if descriptor.location().init == 0 {
569 return Err(IoError::NotFound {
570 backtrace: BackTrace::capture(),
571 reason: "Reserved memory isn't initialized".into(),
572 });
573 }
574
575 let pool_index = descriptor.location().pool as usize;
576 if pool_index >= self.pools.len() {
577 return self.persistent.bind(reserved, assigned, cursor);
578 }
579
580 self.pools
581 .get_mut(pool_index)
582 .map(|p| p.bind(reserved, assigned, cursor))
583 .ok_or_else(|| IoError::NotFound {
584 backtrace: BackTrace::capture(),
585 reason: format!("Memory pool {} doesn't exist", pool_index).into(),
586 })?
587 }
588}
589
590impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
591 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
592 f.write_str("\n# MemoryManagement\n\n")?;
593 f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
594 f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
595 f.write_str("\n## Dynamic\n\n")?;
596
597 for pool in self.pools.iter() {
598 match pool {
599 DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
600 DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
601 }
602 }
603 let memory_usage = self.memory_usage();
604 f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
605
606 Ok(())
607 }
608}
609
610impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
611 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
612 f.write_str(
613 alloc::format!(
614 "DynamicMemoryManagement {:?}",
615 core::any::type_name::<Storage>(),
616 )
617 .as_str(),
618 )
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use crate::{
626 memory_management::MemoryManagement,
627 storage::{BytesStorage, ComputeStorage, StorageHandle, StorageId},
628 };
629 use alloc::vec;
630
631 struct LimitedStorage {
632 storage: BytesStorage,
633 max_alloc_size: u64,
634 alloc_calls: u64,
635 }
636
637 impl ComputeStorage for LimitedStorage {
638 type Resource = <BytesStorage as ComputeStorage>::Resource;
639
640 fn alignment(&self) -> usize {
641 self.storage.alignment()
642 }
643
644 fn get(&mut self, handle: &StorageHandle) -> Self::Resource {
645 self.storage.get(handle)
646 }
647
648 fn alloc(&mut self, size: u64) -> Result<StorageHandle, IoError> {
649 self.alloc_calls += 1;
650 if size > self.max_alloc_size {
651 return Err(IoError::OutOfMemory {
652 size,
653 backtrace: BackTrace::capture(),
654 });
655 }
656 self.storage.alloc(size)
657 }
658
659 fn dealloc(&mut self, id: StorageId) {
660 self.storage.dealloc(id)
661 }
662
663 fn flush(&mut self) {
664 self.storage.flush()
665 }
666 }
667
668 const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
669 max_page_size: 128 * 1024 * 1024,
670 alignment: 32,
671 };
672
673 fn options() -> MemoryManagementOptions {
674 MemoryManagementOptions {
675 name: "test".into(),
676 memory: MemoryAllocationOption::FromConfig,
677 }
678 }
679
680 #[test_log::test]
682 #[cfg(not(exclusive_memory_only))]
683 fn test_handle_mutability() {
684 let mut memory_management = MemoryManagement::from_configuration(
685 BytesStorage::default(),
686 &DUMMY_MEM_PROPS,
687 MemoryConfiguration::SubSlices,
688 Arc::new(ServerLogger::default()),
689 options(),
690 );
691 let handle = memory_management.reserve(10).unwrap();
692 let other_ref = handle.clone();
693 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
694 drop(other_ref);
695 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
696 }
697
698 #[test_log::test]
700 #[cfg(not(exclusive_memory_only))]
701 fn test_memory_usage() {
702 let max_page_size = 512;
703
704 let mut memory_management = MemoryManagement::from_configuration(
705 BytesStorage::default(),
706 &DUMMY_MEM_PROPS,
707 MemoryConfiguration::Custom {
708 pool_options: vec![MemoryPoolOptions {
709 pool_type: PoolType::ExclusivePages {
710 max_alloc_size: max_page_size,
711 },
712 dealloc_period: None,
713 }],
714 },
715 Arc::new(ServerLogger::default()),
716 options(),
717 );
718 let handle = memory_management.reserve(100);
719 let usage = memory_management.memory_usage();
720
721 assert_eq!(usage.bytes_in_use, 100);
722 assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
723
724 drop(handle);
726 let _handle = memory_management.reserve(100);
727 let usage_new = memory_management.memory_usage();
728 assert_eq!(usage, usage_new);
729 }
730
731 #[test_log::test]
732 fn alloc_two_chunks_on_one_page() {
733 let page_size = 2048;
734
735 let mut memory_management = MemoryManagement::from_configuration(
736 BytesStorage::default(),
737 &DUMMY_MEM_PROPS,
738 MemoryConfiguration::Custom {
739 pool_options: vec![MemoryPoolOptions {
740 pool_type: PoolType::SlicedPages {
741 page_size,
742 max_slice_size: page_size,
743 },
744 dealloc_period: None,
745 }],
746 },
747 Arc::new(ServerLogger::default()),
748 options(),
749 );
750
751 let alloc_size = 512;
752 let _handle = memory_management.reserve(alloc_size);
753 let _new_handle = memory_management.reserve(alloc_size);
754
755 let usage = memory_management.memory_usage();
756 assert_eq!(usage.number_allocs, 2);
757 assert_eq!(usage.bytes_in_use, alloc_size * 2);
758 assert_eq!(usage.bytes_reserved, page_size);
759 }
760
761 #[test_log::test]
762 fn sliced_pool_allocates_large_slices_exactly() {
763 let mut memory_management = MemoryManagement::from_configuration(
764 BytesStorage::default(),
765 &DUMMY_MEM_PROPS,
766 MemoryConfiguration::Custom {
767 pool_options: vec![MemoryPoolOptions {
768 pool_type: PoolType::SlicedPages {
769 page_size: 1024,
770 max_slice_size: 1024,
771 },
772 dealloc_period: None,
773 }],
774 },
775 Arc::new(ServerLogger::default()),
776 options(),
777 );
778
779 let _handle = memory_management.reserve(400).unwrap();
780 let usage = memory_management.memory_usage();
781 assert_eq!(usage.bytes_in_use, 400);
782 assert_eq!(usage.bytes_reserved, 416);
783 }
784
785 #[test_log::test]
786 fn sliced_pool_falls_back_to_an_exact_page_after_oom() {
787 let mut memory_management = MemoryManagement::from_configuration(
788 LimitedStorage {
789 storage: BytesStorage::default(),
790 max_alloc_size: 512,
791 alloc_calls: 0,
792 },
793 &DUMMY_MEM_PROPS,
794 MemoryConfiguration::Custom {
795 pool_options: vec![MemoryPoolOptions {
796 pool_type: PoolType::SlicedPages {
797 page_size: 1024,
798 max_slice_size: 1024,
799 },
800 dealloc_period: None,
801 }],
802 },
803 Arc::new(ServerLogger::default()),
804 options(),
805 );
806
807 let _handle = memory_management.reserve(200).unwrap();
808 let usage = memory_management.memory_usage();
809 assert_eq!(usage.bytes_in_use, 200);
810 assert_eq!(usage.bytes_reserved, 224);
811 }
812
813 #[test_log::test]
814 fn sliced_pool_oom_fallback_tries_an_exact_page_at_most_once() {
815 let mut memory_management = MemoryManagement::from_configuration(
816 LimitedStorage {
817 storage: BytesStorage::default(),
818 max_alloc_size: 100,
819 alloc_calls: 0,
820 },
821 &DUMMY_MEM_PROPS,
822 MemoryConfiguration::Custom {
823 pool_options: vec![MemoryPoolOptions {
824 pool_type: PoolType::SlicedPages {
825 page_size: 1024,
826 max_slice_size: 1024,
827 },
828 dealloc_period: None,
829 }],
830 },
831 Arc::new(ServerLogger::default()),
832 options(),
833 );
834
835 let result = memory_management.reserve(200);
837 assert!(matches!(result, Err(IoError::OutOfMemory { .. })));
838 assert_eq!(memory_management.storage().alloc_calls, 2);
839
840 let result = memory_management.reserve(400);
842 assert!(matches!(result, Err(IoError::OutOfMemory { .. })));
843 assert_eq!(memory_management.storage().alloc_calls, 3);
844 assert_eq!(memory_management.memory_usage().bytes_reserved, 0);
845 }
846
847 #[test_log::test]
848 #[cfg(not(exclusive_memory_only))]
849 fn subslices_reserves_large_allocations_exactly() {
850 const MB: u64 = 1024 * 1024;
851 let mut memory_management = MemoryManagement::from_configuration(
852 BytesStorage::default(),
853 &DUMMY_MEM_PROPS,
854 MemoryConfiguration::SubSlices,
855 Arc::new(ServerLogger::default()),
856 options(),
857 );
858
859 let _handle = memory_management.reserve(20 * MB).unwrap();
862 let usage = memory_management.memory_usage();
863 assert_eq!(usage.bytes_in_use, 20 * MB);
864 assert_eq!(usage.bytes_reserved, 20 * MB);
865 }
866
867 #[test_log::test]
868 fn exclusive_pool_reuses_the_tightest_free_page() {
869 let mut memory_management = MemoryManagement::from_configuration(
870 BytesStorage::default(),
871 &DUMMY_MEM_PROPS,
872 MemoryConfiguration::Custom {
873 pool_options: vec![MemoryPoolOptions {
874 pool_type: PoolType::ExclusivePages {
875 max_alloc_size: 4096,
876 },
877 dealloc_period: None,
878 }],
879 },
880 Arc::new(ServerLogger::default()),
881 options(),
882 );
883
884 let large = memory_management.reserve(1024).unwrap();
885 let small = memory_management.reserve(512).unwrap();
886 drop(large);
887 drop(small);
888
889 let _a = memory_management.reserve(400).unwrap();
891 let _b = memory_management.reserve(1000).unwrap();
892 let usage = memory_management.memory_usage();
893 assert_eq!(usage.number_allocs, 2);
894 assert_eq!(usage.bytes_reserved, 1024 + 512);
895 }
896
897 #[test_log::test]
898 fn alloc_reuses_storage() {
899 let page_size = 512;
901
902 let mut memory_management = MemoryManagement::from_configuration(
903 BytesStorage::default(),
904 &DUMMY_MEM_PROPS,
905 MemoryConfiguration::Custom {
906 pool_options: vec![MemoryPoolOptions {
907 pool_type: PoolType::SlicedPages {
908 page_size,
909 max_slice_size: page_size,
910 },
911 dealloc_period: None,
912 }],
913 },
914 Arc::new(ServerLogger::default()),
915 options(),
916 );
917
918 let alloc_size = 512;
919 let _handle = memory_management.reserve(alloc_size);
920 drop(_handle);
921 let _new_handle = memory_management.reserve(alloc_size);
922
923 let usage = memory_management.memory_usage();
924 assert_eq!(usage.number_allocs, 1);
925 assert_eq!(usage.bytes_in_use, alloc_size);
926 assert_eq!(usage.bytes_reserved, page_size);
927 }
928
929 #[test_log::test]
930 fn alloc_allocs_new_storage() {
931 let page_size = 1024;
932
933 let mut memory_management = MemoryManagement::from_configuration(
934 BytesStorage::default(),
935 &DUMMY_MEM_PROPS,
936 MemoryConfiguration::Custom {
937 pool_options: vec![MemoryPoolOptions {
938 pool_type: PoolType::SlicedPages {
939 page_size,
940 max_slice_size: page_size,
941 },
942 dealloc_period: None,
943 }],
944 },
945 Arc::new(ServerLogger::default()),
946 options(),
947 );
948
949 let alloc_size = 768;
950 let _handle = memory_management.reserve(alloc_size);
951 let _new_handle = memory_management.reserve(alloc_size);
952
953 let usage = memory_management.memory_usage();
954 assert_eq!(usage.number_allocs, 2);
955 assert_eq!(usage.bytes_in_use, alloc_size * 2);
956 assert_eq!(usage.bytes_reserved, alloc_size * 2);
957 }
958
959 #[test_log::test]
960 fn alloc_respects_alignment_size() {
961 let page_size = 500;
962 let mut memory_management = MemoryManagement::from_configuration(
963 BytesStorage::default(),
964 &MemoryDeviceProperties {
965 max_page_size: page_size,
966 alignment: 50,
967 },
968 MemoryConfiguration::Custom {
969 pool_options: vec![MemoryPoolOptions {
970 pool_type: PoolType::SlicedPages {
971 page_size,
972 max_slice_size: page_size,
973 },
974 dealloc_period: None,
975 }],
976 },
977 Arc::new(ServerLogger::default()),
978 options(),
979 );
980 let alloc_size = 40;
981 let _handle = memory_management.reserve(alloc_size);
982 let _new_handle = memory_management.reserve(alloc_size);
983 let usage = memory_management.memory_usage();
984 assert_eq!(usage.bytes_padding, 10 * 2);
986 }
987
988 #[test_log::test]
989 fn allocs_on_correct_page() {
990 let sizes = [100, 200, 300, 400];
991
992 let pools = sizes
993 .iter()
994 .map(|size| MemoryPoolOptions {
995 pool_type: PoolType::SlicedPages {
996 page_size: *size,
997 max_slice_size: *size,
998 },
999 dealloc_period: None,
1000 })
1001 .collect();
1002 let mut memory_management = MemoryManagement::from_configuration(
1003 BytesStorage::default(),
1004 &MemoryDeviceProperties {
1005 max_page_size: 128 * 1024 * 1024,
1006 alignment: 10,
1007 },
1008 MemoryConfiguration::Custom {
1009 pool_options: pools,
1010 },
1011 Arc::new(ServerLogger::default()),
1012 options(),
1013 );
1014 let alloc_sizes = [50, 150, 250, 350];
1016 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1017
1018 let usage = memory_management.memory_usage();
1019
1020 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1022 assert_eq!(usage.bytes_reserved, alloc_sizes.iter().sum::<u64>());
1023 }
1024
1025 #[test_log::test]
1026 #[cfg(not(exclusive_memory_only))]
1027 fn allocate_deallocate_reallocate() {
1028 let mut memory_management = MemoryManagement::from_configuration(
1029 BytesStorage::default(),
1030 &MemoryDeviceProperties {
1031 max_page_size: 128 * 1024 * 1024,
1032 alignment: 32,
1033 },
1034 MemoryConfiguration::SubSlices,
1035 Arc::new(ServerLogger::default()),
1036 options(),
1037 );
1038 let handles: Vec<_> = (0..5)
1040 .map(|i| memory_management.reserve(1000 * (i + 1)))
1041 .collect();
1042 let usage_before = memory_management.memory_usage();
1043 drop(handles);
1045 let _new_handles: Vec<_> = (0..5)
1047 .map(|i| memory_management.reserve(1000 * (i + 1)))
1048 .collect();
1049 let usage_after = memory_management.memory_usage();
1050 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1051 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1052 assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
1054 }
1055
1056 #[test_log::test]
1057 #[cfg(not(exclusive_memory_only))]
1058 fn test_fragmentation_resistance() {
1059 let mut memory_management = MemoryManagement::from_configuration(
1060 BytesStorage::default(),
1061 &MemoryDeviceProperties {
1062 max_page_size: 128 * 1024 * 1024,
1063 alignment: 32,
1064 },
1065 MemoryConfiguration::SubSlices,
1066 Arc::new(ServerLogger::default()),
1067 options(),
1068 );
1069 let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
1071 let handles: Vec<_> = sizes
1072 .iter()
1073 .map(|&size| memory_management.reserve(size).unwrap())
1074 .collect();
1075 let usage_before = memory_management.memory_usage();
1076 for i in (0..handles.len()).step_by(2) {
1078 drop(handles[i].clone());
1079 }
1080 for &size in &sizes[0..sizes.len() / 2] {
1082 memory_management.reserve(size).unwrap();
1083 }
1084 let usage_after = memory_management.memory_usage();
1085 assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
1087 }
1088
1089 #[test_log::test]
1091 fn noslice_test_handle_mutability() {
1092 let mut memory_management = MemoryManagement::from_configuration(
1093 BytesStorage::default(),
1094 &(MemoryDeviceProperties {
1095 max_page_size: 128 * 1024 * 1024,
1096 alignment: 32,
1097 }),
1098 MemoryConfiguration::ExclusivePages,
1099 Arc::new(ServerLogger::default()),
1100 options(),
1101 );
1102 let handle = memory_management.reserve(10).unwrap();
1103 let other_ref = handle.clone();
1104 assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
1105 drop(other_ref);
1106 assert!(handle.can_mut(), "Handle should be mut when only one ref.");
1107 }
1108
1109 #[test_log::test]
1110 fn noslice_alloc_two_chunk() {
1111 let mut memory_management = MemoryManagement::from_configuration(
1112 BytesStorage::default(),
1113 &DUMMY_MEM_PROPS,
1114 MemoryConfiguration::Custom {
1115 pool_options: vec![MemoryPoolOptions {
1116 pool_type: PoolType::ExclusivePages {
1117 max_alloc_size: 1024,
1118 },
1119 dealloc_period: None,
1120 }],
1121 },
1122 Arc::new(ServerLogger::default()),
1123 options(),
1124 );
1125
1126 let alloc_size = 512;
1127 let _handle = memory_management.reserve(alloc_size);
1128 let _new_handle = memory_management.reserve(alloc_size);
1129
1130 let usage = memory_management.memory_usage();
1131 assert_eq!(usage.number_allocs, 2);
1132 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1133 assert!(usage.bytes_reserved >= alloc_size * 2);
1134 }
1135
1136 #[test_log::test]
1137 fn noslice_alloc_reuses_storage() {
1138 let mut memory_management = MemoryManagement::from_configuration(
1140 BytesStorage::default(),
1141 &DUMMY_MEM_PROPS,
1142 MemoryConfiguration::Custom {
1143 pool_options: vec![MemoryPoolOptions {
1144 pool_type: PoolType::ExclusivePages {
1145 max_alloc_size: 1024,
1146 },
1147 dealloc_period: None,
1148 }],
1149 },
1150 Arc::new(ServerLogger::default()),
1151 options(),
1152 );
1153
1154 let alloc_size = 512;
1155 let _handle = memory_management.reserve(alloc_size);
1156 drop(_handle);
1157 let _new_handle = memory_management.reserve(alloc_size);
1158
1159 let usage = memory_management.memory_usage();
1160 assert_eq!(usage.number_allocs, 1);
1161 assert_eq!(usage.bytes_in_use, alloc_size);
1162 assert!(usage.bytes_reserved >= alloc_size);
1163 }
1164
1165 #[test_log::test]
1166 fn noslice_alloc_allocs_new_storage() {
1167 let mut memory_management = MemoryManagement::from_configuration(
1168 BytesStorage::default(),
1169 &DUMMY_MEM_PROPS,
1170 MemoryConfiguration::Custom {
1171 pool_options: vec![MemoryPoolOptions {
1172 pool_type: PoolType::ExclusivePages {
1173 max_alloc_size: 1024,
1174 },
1175 dealloc_period: None,
1176 }],
1177 },
1178 Arc::new(ServerLogger::default()),
1179 options(),
1180 );
1181
1182 let alloc_size = 768;
1183 let _handle = memory_management.reserve(alloc_size);
1184 let _new_handle = memory_management.reserve(alloc_size);
1185 let usage = memory_management.memory_usage();
1186 assert_eq!(usage.number_allocs, 2);
1187 assert_eq!(usage.bytes_in_use, alloc_size * 2);
1188 assert!(usage.bytes_reserved >= alloc_size * 2);
1189 }
1190
1191 #[test_log::test]
1192 fn noslice_alloc_respects_alignment_size() {
1193 let mut memory_management = MemoryManagement::from_configuration(
1194 BytesStorage::default(),
1195 &MemoryDeviceProperties {
1196 max_page_size: DUMMY_MEM_PROPS.max_page_size,
1197 alignment: 50,
1198 },
1199 MemoryConfiguration::Custom {
1200 pool_options: vec![MemoryPoolOptions {
1201 pool_type: PoolType::ExclusivePages {
1202 max_alloc_size: 50 * 20,
1203 },
1204 dealloc_period: None,
1205 }],
1206 },
1207 Arc::new(ServerLogger::default()),
1208 options(),
1209 );
1210 let alloc_size = 40;
1211 let _handle = memory_management.reserve(alloc_size);
1212 let _new_handle = memory_management.reserve(alloc_size);
1213 let usage = memory_management.memory_usage();
1214 assert_eq!(usage.bytes_padding, 10 * 2);
1216 assert_eq!(usage.bytes_reserved, 50 * 2);
1217 }
1218
1219 #[test_log::test]
1220 fn noslice_allocs_on_correct_page() {
1221 let pools = [100, 200, 300, 400]
1222 .iter()
1223 .map(|&size| MemoryPoolOptions {
1224 pool_type: PoolType::SlicedPages {
1225 page_size: size,
1226 max_slice_size: size,
1227 },
1228 dealloc_period: None,
1229 })
1230 .collect();
1231 let mut memory_management = MemoryManagement::from_configuration(
1232 BytesStorage::default(),
1233 &MemoryDeviceProperties {
1234 max_page_size: DUMMY_MEM_PROPS.max_page_size,
1235 alignment: 10,
1236 },
1237 MemoryConfiguration::Custom {
1238 pool_options: pools,
1239 },
1240 Arc::new(ServerLogger::default()),
1241 options(),
1242 );
1243 let alloc_sizes = [50, 150, 250, 350];
1245 let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1246 let usage = memory_management.memory_usage();
1247 assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1249 }
1250
1251 #[test_log::test]
1252 fn noslice_allocate_deallocate_reallocate() {
1253 let mut memory_management = MemoryManagement::from_configuration(
1254 BytesStorage::default(),
1255 &MemoryDeviceProperties {
1256 max_page_size: 128 * 1024 * 1024,
1257 alignment: 32,
1258 },
1259 MemoryConfiguration::ExclusivePages,
1260 Arc::new(ServerLogger::default()),
1261 options(),
1262 );
1263 let handles: Vec<_> = (0..5)
1265 .map(|i| memory_management.reserve(1000 * (i + 1)))
1266 .collect();
1267 let usage_before = memory_management.memory_usage();
1268 drop(handles);
1270 let _new_handles: Vec<_> = (0..5)
1272 .map(|i| memory_management.reserve(1000 * (i + 1)))
1273 .collect();
1274 let usage_after = memory_management.memory_usage();
1275 assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1276 assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1277 assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
1278 }
1279}