Skip to main content

ruda_runtime/runtime/memory_management/
memory_manage.rs

1use super::{
2    MemoryConfiguration, MemoryPoolOptions, MemoryUsage, PoolType,
3    memory_pool::{ExclusiveMemoryPool, MemoryPool, PersistentPool, SlicedPool},
4};
5use crate::runtime::{
6    config::{
7        RudaRuntimeConfig, 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 ruda_core::{backtrace::BackTrace, stub::Arc};
22use ruda_core::ir::MemoryDeviceProperties;
23
24pub use super::memory_pool::{ManagedMemoryBinding, handle::*};
25
26// These are 288 bytes vs 64 bytes. Adding boxing isn't really worth
27// saving the 200 bytes.
28#[allow(clippy::large_enum_variant)]
29enum DynamicPool {
30    Sliced(SlicedPool),
31    Exclusive(ExclusiveMemoryPool),
32}
33
34impl DynamicPool {
35    fn find_at(&self, location: MemoryLocation) -> Result<&Slice, IoError> {
36        match self {
37            DynamicPool::Sliced(pool) => pool.find_at(location),
38            DynamicPool::Exclusive(pool) => pool.find_at(location),
39        }
40    }
41}
42
43impl MemoryPool for DynamicPool {
44    fn accept(&self, size: u64) -> bool {
45        match self {
46            DynamicPool::Sliced(pool) => pool.accept(size),
47            DynamicPool::Exclusive(pool) => pool.accept(size),
48        }
49    }
50
51    fn find(&self, binding: &ManagedMemoryBinding) -> Result<&Slice, IoError> {
52        self.find_at(binding.descriptor().location())
53    }
54
55    #[cfg_attr(feature = "runtime-tracing", tracing::instrument(level = "trace", skip(self)))]
56    fn try_reserve(&mut self, size: u64) -> Option<ManagedMemoryHandle> {
57        match self {
58            DynamicPool::Sliced(m) => m.try_reserve(size),
59            DynamicPool::Exclusive(m) => m.try_reserve(size),
60        }
61    }
62
63    #[cfg_attr(
64        feature = "runtime-tracing",
65        tracing::instrument(level = "trace", skip(self, storage))
66    )]
67    fn alloc<Storage: ComputeStorage>(
68        &mut self,
69        storage: &mut Storage,
70        size: u64,
71    ) -> Result<ManagedMemoryHandle, IoError> {
72        match self {
73            DynamicPool::Sliced(m) => m.alloc(storage, size),
74            DynamicPool::Exclusive(m) => m.alloc(storage, size),
75        }
76    }
77
78    fn get_memory_usage(&self) -> MemoryUsage {
79        match self {
80            DynamicPool::Sliced(m) => m.get_memory_usage(),
81            DynamicPool::Exclusive(m) => m.get_memory_usage(),
82        }
83    }
84
85    fn cleanup<Storage: ComputeStorage>(
86        &mut self,
87        storage: &mut Storage,
88        alloc_nr: u64,
89        explicit: bool,
90    ) {
91        match self {
92            DynamicPool::Sliced(m) => m.cleanup(storage, alloc_nr, explicit),
93            DynamicPool::Exclusive(m) => m.cleanup(storage, alloc_nr, explicit),
94        };
95        storage.flush();
96    }
97
98    fn bind(
99        &mut self,
100        reserved: ManagedMemoryHandle,
101        assigned: ManagedMemoryHandle,
102        cursor: u64,
103    ) -> Result<(), IoError> {
104        match self {
105            DynamicPool::Sliced(m) => m.bind(reserved, assigned, cursor),
106            DynamicPool::Exclusive(m) => m.bind(reserved, assigned, cursor),
107        }
108    }
109}
110
111#[derive(Default, Clone, Copy, Debug)]
112/// The mode of allocation used.
113pub enum MemoryAllocationMode {
114    /// Use the automatic memory management strategy for allocation.
115    #[default]
116    Auto,
117    /// Use a persistent memory management strategy, meaning that all allocations are for data that is
118    /// likely never going to be freed.
119    Persistent,
120}
121
122/// Reserves and keeps track of chunks of memory in the storage, and slices upon these chunks.
123pub struct MemoryManagement<Storage> {
124    name: String,
125    persistent: PersistentPool,
126    pools: Vec<DynamicPool>,
127    storage: Storage,
128    alloc_reserve_count: u64,
129    mode: MemoryAllocationMode,
130    config: PersistentMemory,
131    logger: Arc<ServerLogger>,
132}
133
134fn generate_bucket_sizes(
135    start_size: u64,
136    end_size: u64,
137    max_buckets: usize,
138    alignment: u64,
139) -> Vec<u64> {
140    let mut buckets = Vec::with_capacity(max_buckets);
141    let log_min = (start_size as f64).ln();
142    let log_max = (end_size as f64).ln();
143    let log_range = log_max - log_min;
144
145    // Pure exponential performed best, but let's try slightly denser in lower-mid range
146    for i in 0..max_buckets {
147        let p = i as f64 / (max_buckets - 1) as f64;
148        // Slight bias toward lower-mid range with less aggressive curve than sigmoid
149        let log_size = log_min + log_range * p;
150        let size = log_size.exp() as u64;
151        let aligned_size = size.next_multiple_of(alignment);
152        buckets.push(aligned_size);
153    }
154
155    buckets.dedup();
156    buckets
157}
158
159const DEALLOC_SCALE_MB: u64 = 1024 * 1024 * 1024;
160const BASE_DEALLOC_PERIOD: u64 = 5000;
161
162/// The options for creating a new [`MemoryManagement`] instance.
163#[derive(Debug)]
164pub struct MemoryManagementOptions {
165    /// The name of the memory management.
166    name: String,
167    /// The [`MemoryAllocationOption`] used by this instance.
168    memory: MemoryAllocationOption,
169}
170
171impl MemoryManagementOptions {
172    /// Creates a new [`MemoryManagementOptions`].
173    pub fn new<S: Into<String>>(name: S) -> Self {
174        Self {
175            name: name.into(),
176            memory: MemoryAllocationOption::FromConfig,
177        }
178    }
179
180    /// Forces the [`MemoryAllocationMode`] during execution to always be the provided one.
181    pub fn mode(mut self, mode: MemoryAllocationMode) -> Self {
182        self.memory = MemoryAllocationOption::Provided(mode);
183        self
184    }
185}
186
187#[derive(Default, Debug)]
188/// Determines which [`MemoryAllocationMode`] is used during allocations.
189enum MemoryAllocationOption {
190    #[default]
191    /// Uses the [`GlobalConfig`] to determine the mode of allocation.
192    FromConfig,
193    /// Use the provided [`MemoryAllocationMode`].
194    Provided(MemoryAllocationMode),
195}
196
197impl<Storage: ComputeStorage> MemoryManagement<Storage> {
198    /// Creates the options from device limits.
199    pub fn from_configuration(
200        storage: Storage,
201        properties: &MemoryDeviceProperties,
202        config: MemoryConfiguration,
203        logger: Arc<ServerLogger>,
204        options: MemoryManagementOptions,
205    ) -> Self {
206        let pool_options = match config {
207            #[cfg(not(exclusive_memory_only))]
208            MemoryConfiguration::SubSlices => {
209                // Round chunk size to be aligned.
210                let memory_alignment = properties.alignment;
211                let max_page = properties.max_page_size;
212                let mut pools = Vec::new();
213
214                const MB: u64 = 1024 * 1024;
215
216                // Add in a pool for allocations that are smaller than the min alignment,
217                // as they can't use offsets at all (on wgpu at least).
218                pools.push(MemoryPoolOptions {
219                    pool_type: PoolType::ExclusivePages { max_alloc_size: 0 },
220                    dealloc_period: None,
221                });
222
223                let mut current = max_page;
224                let mut max_sizes = vec![];
225                let mut page_sizes = vec![];
226                let mut base = pools.len() as u32;
227
228                while current >= 32 * MB {
229                    current /= 4;
230
231                    // Make sure every pool has an aligned size.
232                    current = current.next_multiple_of(memory_alignment);
233
234                    max_sizes.push(current / 2u64.pow(base));
235                    page_sizes.push(current);
236                    base += 1;
237                }
238
239                max_sizes.reverse();
240                page_sizes.reverse();
241
242                for i in 0..max_sizes.len() {
243                    let max = max_sizes[i];
244                    let page_size = page_sizes[i];
245
246                    pools.push(MemoryPoolOptions {
247                        // Creating max slices lower than the chunk size reduces fragmentation.
248                        pool_type: PoolType::SlicedPages {
249                            page_size,
250                            max_slice_size: max,
251                        },
252                        dealloc_period: None,
253                    });
254                }
255
256                // Add pools from big to small.
257                pools.push(MemoryPoolOptions {
258                    pool_type: PoolType::SlicedPages {
259                        page_size: max_page / memory_alignment * memory_alignment,
260                        max_slice_size: max_page / memory_alignment * memory_alignment,
261                    },
262                    dealloc_period: None,
263                });
264                pools
265            }
266            MemoryConfiguration::ExclusivePages => {
267                // Add all bin sizes. Nb: because of alignment some buckets
268                // end up as the same size, so only want unique ones,
269                // but also keep the order, so a BTree will do.
270                const MIN_BUCKET_SIZE: u64 = 1024 * 32;
271                const NUM_POOLS: usize = 24;
272
273                let sizes = generate_bucket_sizes(
274                    MIN_BUCKET_SIZE,
275                    properties.max_page_size,
276                    NUM_POOLS,
277                    properties.alignment,
278                );
279
280                sizes
281                    .iter()
282                    .map(|&size| {
283                        let dealloc_period = (BASE_DEALLOC_PERIOD as f64
284                            * (1.0 + size as f64 / (DEALLOC_SCALE_MB as f64)).round())
285                            as u64;
286
287                        MemoryPoolOptions {
288                            pool_type: PoolType::ExclusivePages {
289                                max_alloc_size: size,
290                            },
291                            dealloc_period: Some(dealloc_period),
292                        }
293                    })
294                    .collect()
295            }
296            MemoryConfiguration::Custom { pool_options } => pool_options,
297        };
298
299        logger.log_memory(
300            |level| !matches!(level, MemoryLogLevel::Disabled),
301            || {
302                let mut msg = String::new();
303                for pool in pool_options.iter() {
304                    msg += &format!("[{}] Using memory pool: \n {pool:?}\n", options.name);
305                }
306                msg
307            },
308        );
309
310        let pools: Vec<_> = pool_options
311            .iter()
312            .enumerate()
313            .map(|(pool_pos, options)| {
314                let pool_pos = pool_pos as u8;
315
316                match options.pool_type {
317                    PoolType::SlicedPages {
318                        page_size,
319                        max_slice_size,
320                    } => DynamicPool::Sliced(SlicedPool::new(
321                        page_size,
322                        max_slice_size,
323                        properties.alignment,
324                        pool_pos,
325                    )),
326                    PoolType::ExclusivePages { max_alloc_size } => {
327                        DynamicPool::Exclusive(ExclusiveMemoryPool::new(
328                            max_alloc_size,
329                            properties.alignment,
330                            options.dealloc_period.unwrap_or(u64::MAX),
331                            pool_pos,
332                        ))
333                    }
334                }
335            })
336            .collect();
337
338        let config = RudaRuntimeConfig::get().memory.persistent_memory.clone();
339
340        let mode = match options.memory {
341            MemoryAllocationOption::Provided(mode) => mode,
342            MemoryAllocationOption::FromConfig => match config {
343                PersistentMemory::Enabled => MemoryAllocationMode::Auto,
344                PersistentMemory::Disabled => MemoryAllocationMode::Auto,
345                PersistentMemory::Enforced => MemoryAllocationMode::Persistent,
346            },
347        };
348
349        Self {
350            name: options.name,
351            persistent: PersistentPool::new(
352                properties.max_page_size,
353                properties.alignment,
354                pools.len() as u8,
355            ),
356            pools,
357            storage,
358            alloc_reserve_count: 0,
359            mode,
360            config,
361            logger,
362        }
363    }
364
365    /// Change the mode of allocation.
366    pub fn mode(&mut self, mode: MemoryAllocationMode) {
367        // We override the mode based on the ruda config.
368        let mode = match self.config {
369            PersistentMemory::Enabled => mode,
370            PersistentMemory::Disabled | PersistentMemory::Enforced => return,
371        };
372
373        self.logger.log_memory(
374            |level| !matches!(level, MemoryLogLevel::Disabled),
375            || {
376                format!(
377                    "[{}] Setting memory allocation mode: from {:?} => {mode:?}",
378                    self.name, self.mode
379                )
380            },
381        );
382        self.mode = mode;
383    }
384
385    /// Cleanup allocations in pools that are deemed unnecessary.
386    pub fn cleanup(&mut self, explicit: bool) {
387        self.logger.log_memory(
388            |level| !matches!(level, MemoryLogLevel::Disabled) && explicit,
389            || "Manual memory cleanup ...".to_string(),
390        );
391
392        self.persistent
393            .cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
394
395        for pool in self.pools.iter_mut() {
396            pool.cleanup(&mut self.storage, self.alloc_reserve_count, explicit);
397        }
398    }
399
400    /// Returns the storage from the specified binding
401    pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
402        let slice = self.find(binding)?;
403        Ok(slice.cursor)
404    }
405
406    /// Returns the storage from the specified binding
407    fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
408        let location = binding.descriptor().location();
409
410        if location.pool >= self.pools.len() as u8 {
411            return self.persistent.find_at(location);
412        }
413
414        let pool =
415            self.pools
416                .get(location.pool as usize)
417                .ok_or_else(|| IoError::NotFound {
418                    backtrace: BackTrace::capture(),
419                    reason: format!("Pool {} doesn't exist", location.pool).into(),
420                })?;
421
422        let slice = pool.find_at(location)?;
423
424        assert_eq!(slice.handle.descriptor(), binding.descriptor());
425
426        Ok(slice)
427    }
428
429    /// Returns the storage from the specified binding
430    pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
431        let slice = self.find(binding)?;
432        Ok(slice.storage.clone())
433    }
434
435    /// Returns the resource from the storage at the specified handle
436    pub fn get_resource(
437        &mut self,
438        binding: ManagedMemoryBinding,
439        offset_start: Option<u64>,
440        offset_end: Option<u64>,
441    ) -> Result<Storage::Resource, IoError> {
442        let handle = self.get_storage(binding)?;
443
444        let handle = match offset_start {
445            Some(offset) => handle.offset_start(offset),
446            None => handle,
447        };
448        let handle = match offset_end {
449            Some(offset) => handle.offset_end(offset),
450            None => handle,
451        };
452        Ok(self.storage().get(&handle))
453    }
454
455    /// Finds a spot in memory for a resource with the given size in bytes, and returns a handle to it
456    #[cfg_attr(feature = "runtime-tracing", tracing::instrument(level = "trace", skip(self)))]
457    pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
458        // If this happens every nanosecond, counts overflows after 585 years, so not worth thinking too
459        // hard about overflow here.
460        self.alloc_reserve_count += 1;
461
462        if let Some(val) = self.persistent.try_reserve(size) {
463            self.logger.log_memory(
464                |level| matches!(level, MemoryLogLevel::Full),
465                || {
466                    format!(
467                        "[{}] Reserved memory {size} using persistent memory",
468                        self.name
469                    )
470                },
471            );
472            return Ok(val);
473        }
474
475        if matches!(self.mode, MemoryAllocationMode::Persistent) || self.persistent.has_size(size) {
476            let allocated = self.persistent.alloc(&mut self.storage, size);
477
478            self.logger.log_memory(
479                |level| !matches!(level, MemoryLogLevel::Disabled),
480                || {
481                    format!(
482                        "[{}] Allocated a new memory page using persistent memory, \n{}",
483                        self.name, self,
484                    )
485                },
486            );
487            return allocated;
488        }
489
490        self.logger.log_memory(
491            |level| matches!(level, MemoryLogLevel::Full),
492            || {
493                format!(
494                    "[{}] Reserved memory {} using dynamic pool",
495                    self.name,
496                    BytesFormat::new(size)
497                )
498            },
499        );
500
501        // Find first pool that fits this allocation
502        let pool = self
503            .pools
504            .iter_mut()
505            .find(|p| p.accept(size))
506            .ok_or(IoError::BufferTooBig {
507                size,
508                backtrace: BackTrace::capture(),
509            })?;
510
511        if let Some(slice) = pool.try_reserve(size) {
512            return Ok(slice);
513        }
514
515        let allocated = pool.alloc(&mut self.storage, size);
516
517        self.logger.log_memory(
518            |level| matches!(level, MemoryLogLevel::Full),
519            || {
520                format!(
521                    "[{}], Allocated a new memory page, current usage: \n{}",
522                    self.name, self
523                )
524            },
525        );
526
527        allocated
528    }
529
530    /// Fetch the storage used by the memory manager.
531    ///
532    /// # Notes
533    ///
534    /// The storage should probably not be used for allocations since the handles won't be
535    /// compatible with the ones provided by the current trait. Prefer using the
536    /// [alloc](ComputeStorage::alloc) and [dealloc](ComputeStorage::dealloc) functions.
537    ///
538    /// This is useful if you need to time the deallocations based on async computation, or to
539    /// change the mode of storage for different reasons.
540    pub fn storage(&mut self) -> &mut Storage {
541        &mut self.storage
542    }
543
544    /// Get the current memory usage.
545    pub fn memory_usage(&self) -> MemoryUsage {
546        let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
547            MemoryUsage {
548                number_allocs: 0,
549                bytes_in_use: 0,
550                bytes_padding: 0,
551                bytes_reserved: 0,
552            },
553            |m1, m2| m1.combine(m2),
554        );
555        memory_usage.combine(self.persistent.get_memory_usage())
556    }
557
558    /// Print out a report of the current memory usage.
559    pub fn print_memory_usage(&self) {
560        #[cfg(feature = "runtime-std")]
561        log::info!("{}", self.memory_usage());
562    }
563
564    /// Binds the given [handle](HandleId) to a [`MemorySlot`].
565    pub fn bind(
566        &mut self,
567        reserved: ManagedMemoryHandle,
568        assigned: ManagedMemoryHandle,
569        cursor: u64,
570    ) -> Result<(), IoError> {
571        let descriptor = reserved.descriptor();
572
573        if descriptor.location().init == 0 {
574            return Err(IoError::NotFound {
575                backtrace: BackTrace::capture(),
576                reason: "Reserved memory isn't initialized".into(),
577            });
578        }
579
580        let pool_index = descriptor.location().pool as usize;
581        if pool_index >= self.pools.len() {
582            return self.persistent.bind(reserved, assigned, cursor);
583        }
584
585        self.pools
586            .get_mut(pool_index)
587            .map(|p| p.bind(reserved, assigned, cursor))
588            .ok_or_else(|| IoError::NotFound {
589                backtrace: BackTrace::capture(),
590                reason: format!("Memory pool {} doesn't exist", pool_index).into(),
591            })?
592    }
593}
594
595impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
596    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
597        f.write_str("\n# MemoryManagement\n\n")?;
598        f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
599        f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
600        f.write_str("\n## Dynamic\n\n")?;
601
602        for pool in self.pools.iter() {
603            match pool {
604                DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
605                DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
606            }
607        }
608        let memory_usage = self.memory_usage();
609        f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
610
611        Ok(())
612    }
613}
614
615impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
616    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
617        f.write_str(
618            alloc::format!(
619                "DynamicMemoryManagement {:?}",
620                core::any::type_name::<Storage>(),
621            )
622            .as_str(),
623        )
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use super::*;
630    use crate::runtime::{memory_management::MemoryManagement, storage::BytesStorage};
631    use alloc::vec;
632
633    const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
634        max_page_size: 128 * 1024 * 1024,
635        alignment: 32,
636    };
637
638    fn options() -> MemoryManagementOptions {
639        MemoryManagementOptions {
640            name: "test".into(),
641            memory: MemoryAllocationOption::FromConfig,
642        }
643    }
644
645    // Test pools with slices.
646    #[test_log::test]
647    #[cfg(not(exclusive_memory_only))]
648    fn test_handle_mutability() {
649        let mut memory_management = MemoryManagement::from_configuration(
650            BytesStorage::default(),
651            &DUMMY_MEM_PROPS,
652            MemoryConfiguration::SubSlices,
653            Arc::new(ServerLogger::default()),
654            options(),
655        );
656        let handle = memory_management.reserve(10).unwrap();
657        let other_ref = handle.clone();
658        assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
659        drop(other_ref);
660        assert!(handle.can_mut(), "Handle should be mut when only one ref.");
661    }
662
663    // Test pools with slices.
664    #[test_log::test]
665    #[cfg(not(exclusive_memory_only))]
666    fn test_memory_usage() {
667        let max_page_size = 512;
668
669        let mut memory_management = MemoryManagement::from_configuration(
670            BytesStorage::default(),
671            &DUMMY_MEM_PROPS,
672            MemoryConfiguration::Custom {
673                pool_options: vec![MemoryPoolOptions {
674                    pool_type: PoolType::ExclusivePages {
675                        max_alloc_size: max_page_size,
676                    },
677                    dealloc_period: None,
678                }],
679            },
680            Arc::new(ServerLogger::default()),
681            options(),
682        );
683        let handle = memory_management.reserve(100);
684        let usage = memory_management.memory_usage();
685
686        assert_eq!(usage.bytes_in_use, 100);
687        assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
688
689        // Drop and re-alloc.
690        drop(handle);
691        let _handle = memory_management.reserve(100);
692        let usage_new = memory_management.memory_usage();
693        assert_eq!(usage, usage_new);
694    }
695
696    #[test_log::test]
697    fn alloc_two_chunks_on_one_page() {
698        let page_size = 2048;
699
700        let mut memory_management = MemoryManagement::from_configuration(
701            BytesStorage::default(),
702            &DUMMY_MEM_PROPS,
703            MemoryConfiguration::Custom {
704                pool_options: vec![MemoryPoolOptions {
705                    pool_type: PoolType::SlicedPages {
706                        page_size,
707                        max_slice_size: page_size,
708                    },
709                    dealloc_period: None,
710                }],
711            },
712            Arc::new(ServerLogger::default()),
713            options(),
714        );
715
716        let alloc_size = 512;
717        let _handle = memory_management.reserve(alloc_size);
718        let _new_handle = memory_management.reserve(alloc_size);
719
720        let usage = memory_management.memory_usage();
721        assert_eq!(usage.number_allocs, 2);
722        assert_eq!(usage.bytes_in_use, alloc_size * 2);
723        assert_eq!(usage.bytes_reserved, page_size);
724    }
725
726    #[test_log::test]
727    fn alloc_reuses_storage() {
728        // If no storage is re-used, this will allocate two pages.
729        let page_size = 512;
730
731        let mut memory_management = MemoryManagement::from_configuration(
732            BytesStorage::default(),
733            &DUMMY_MEM_PROPS,
734            MemoryConfiguration::Custom {
735                pool_options: vec![MemoryPoolOptions {
736                    pool_type: PoolType::SlicedPages {
737                        page_size,
738                        max_slice_size: page_size,
739                    },
740                    dealloc_period: None,
741                }],
742            },
743            Arc::new(ServerLogger::default()),
744            options(),
745        );
746
747        let alloc_size = 512;
748        let _handle = memory_management.reserve(alloc_size);
749        drop(_handle);
750        let _new_handle = memory_management.reserve(alloc_size);
751
752        let usage = memory_management.memory_usage();
753        assert_eq!(usage.number_allocs, 1);
754        assert_eq!(usage.bytes_in_use, alloc_size);
755        assert_eq!(usage.bytes_reserved, page_size);
756    }
757
758    #[test_log::test]
759    fn alloc_allocs_new_storage() {
760        let page_size = 1024;
761
762        let mut memory_management = MemoryManagement::from_configuration(
763            BytesStorage::default(),
764            &DUMMY_MEM_PROPS,
765            MemoryConfiguration::Custom {
766                pool_options: vec![MemoryPoolOptions {
767                    pool_type: PoolType::SlicedPages {
768                        page_size,
769                        max_slice_size: page_size,
770                    },
771                    dealloc_period: None,
772                }],
773            },
774            Arc::new(ServerLogger::default()),
775            options(),
776        );
777
778        let alloc_size = 768;
779        let _handle = memory_management.reserve(alloc_size);
780        let _new_handle = memory_management.reserve(alloc_size);
781
782        let usage = memory_management.memory_usage();
783        assert_eq!(usage.number_allocs, 2);
784        assert_eq!(usage.bytes_in_use, alloc_size * 2);
785        assert_eq!(usage.bytes_reserved, page_size * 2);
786    }
787
788    #[test_log::test]
789    fn alloc_respects_alignment_size() {
790        let page_size = 500;
791        let mut memory_management = MemoryManagement::from_configuration(
792            BytesStorage::default(),
793            &MemoryDeviceProperties {
794                max_page_size: page_size,
795                alignment: 50,
796            },
797            MemoryConfiguration::Custom {
798                pool_options: vec![MemoryPoolOptions {
799                    pool_type: PoolType::SlicedPages {
800                        page_size,
801                        max_slice_size: page_size,
802                    },
803                    dealloc_period: None,
804                }],
805            },
806            Arc::new(ServerLogger::default()),
807            options(),
808        );
809        let alloc_size = 40;
810        let _handle = memory_management.reserve(alloc_size);
811        let _new_handle = memory_management.reserve(alloc_size);
812        let usage = memory_management.memory_usage();
813        // Each slice should be aligned to 50 bytes, so 20 padding bytes.
814        assert_eq!(usage.bytes_padding, 10 * 2);
815    }
816
817    #[test_log::test]
818    fn allocs_on_correct_page() {
819        let sizes = [100, 200, 300, 400];
820
821        let pools = sizes
822            .iter()
823            .map(|size| MemoryPoolOptions {
824                pool_type: PoolType::SlicedPages {
825                    page_size: *size,
826                    max_slice_size: *size,
827                },
828                dealloc_period: None,
829            })
830            .collect();
831        let mut memory_management = MemoryManagement::from_configuration(
832            BytesStorage::default(),
833            &MemoryDeviceProperties {
834                max_page_size: 128 * 1024 * 1024,
835                alignment: 10,
836            },
837            MemoryConfiguration::Custom {
838                pool_options: pools,
839            },
840            Arc::new(ServerLogger::default()),
841            options(),
842        );
843        // Allocate one thing on each page.
844        let alloc_sizes = [50, 150, 250, 350];
845        let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
846
847        let usage = memory_management.memory_usage();
848
849        // Total memory should be size of all pages, and no more.
850        assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
851        assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
852    }
853
854    #[test_log::test]
855    #[cfg(not(exclusive_memory_only))]
856    fn allocate_deallocate_reallocate() {
857        let mut memory_management = MemoryManagement::from_configuration(
858            BytesStorage::default(),
859            &MemoryDeviceProperties {
860                max_page_size: 128 * 1024 * 1024,
861                alignment: 32,
862            },
863            MemoryConfiguration::SubSlices,
864            Arc::new(ServerLogger::default()),
865            options(),
866        );
867        // Allocate a bunch
868        let handles: Vec<_> = (0..5)
869            .map(|i| memory_management.reserve(1000 * (i + 1)))
870            .collect();
871        let usage_before = memory_management.memory_usage();
872        // Deallocate
873        drop(handles);
874        // Reallocate
875        let _new_handles: Vec<_> = (0..5)
876            .map(|i| memory_management.reserve(1000 * (i + 1)))
877            .collect();
878        let usage_after = memory_management.memory_usage();
879        assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
880        assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
881        // Usage after can actually be _less_ because of defragging.
882        assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
883    }
884
885    #[test_log::test]
886    #[cfg(not(exclusive_memory_only))]
887    fn test_fragmentation_resistance() {
888        let mut memory_management = MemoryManagement::from_configuration(
889            BytesStorage::default(),
890            &MemoryDeviceProperties {
891                max_page_size: 128 * 1024 * 1024,
892                alignment: 32,
893            },
894            MemoryConfiguration::SubSlices,
895            Arc::new(ServerLogger::default()),
896            options(),
897        );
898        // Allocate a mix of small and large chunks
899        let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
900        let handles: Vec<_> = sizes
901            .iter()
902            .map(|&size| memory_management.reserve(size).unwrap())
903            .collect();
904        let usage_before = memory_management.memory_usage();
905        // Deallocate every other allocation
906        for i in (0..handles.len()).step_by(2) {
907            drop(handles[i].clone());
908        }
909        // Reallocate similar sizes
910        for &size in &sizes[0..sizes.len() / 2] {
911            memory_management.reserve(size).unwrap();
912        }
913        let usage_after = memory_management.memory_usage();
914        // Check that we haven't increased our memory usage significantly
915        assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
916    }
917
918    // Test pools without slices. More or less same as tests above.
919    #[test_log::test]
920    fn noslice_test_handle_mutability() {
921        let mut memory_management = MemoryManagement::from_configuration(
922            BytesStorage::default(),
923            &(MemoryDeviceProperties {
924                max_page_size: 128 * 1024 * 1024,
925                alignment: 32,
926            }),
927            MemoryConfiguration::ExclusivePages,
928            Arc::new(ServerLogger::default()),
929            options(),
930        );
931        let handle = memory_management.reserve(10).unwrap();
932        let other_ref = handle.clone();
933        assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
934        drop(other_ref);
935        assert!(handle.can_mut(), "Handle should be mut when only one ref.");
936    }
937
938    #[test_log::test]
939    fn noslice_alloc_two_chunk() {
940        let mut memory_management = MemoryManagement::from_configuration(
941            BytesStorage::default(),
942            &DUMMY_MEM_PROPS,
943            MemoryConfiguration::Custom {
944                pool_options: vec![MemoryPoolOptions {
945                    pool_type: PoolType::ExclusivePages {
946                        max_alloc_size: 1024,
947                    },
948                    dealloc_period: None,
949                }],
950            },
951            Arc::new(ServerLogger::default()),
952            options(),
953        );
954
955        let alloc_size = 512;
956        let _handle = memory_management.reserve(alloc_size);
957        let _new_handle = memory_management.reserve(alloc_size);
958
959        let usage = memory_management.memory_usage();
960        assert_eq!(usage.number_allocs, 2);
961        assert_eq!(usage.bytes_in_use, alloc_size * 2);
962        assert!(usage.bytes_reserved >= alloc_size * 2);
963    }
964
965    #[test_log::test]
966    fn noslice_alloc_reuses_storage() {
967        // If no storage is re-used, this will allocate two pages.
968        let mut memory_management = MemoryManagement::from_configuration(
969            BytesStorage::default(),
970            &DUMMY_MEM_PROPS,
971            MemoryConfiguration::Custom {
972                pool_options: vec![MemoryPoolOptions {
973                    pool_type: PoolType::ExclusivePages {
974                        max_alloc_size: 1024,
975                    },
976                    dealloc_period: None,
977                }],
978            },
979            Arc::new(ServerLogger::default()),
980            options(),
981        );
982
983        let alloc_size = 512;
984        let _handle = memory_management.reserve(alloc_size);
985        drop(_handle);
986        let _new_handle = memory_management.reserve(alloc_size);
987
988        let usage = memory_management.memory_usage();
989        assert_eq!(usage.number_allocs, 1);
990        assert_eq!(usage.bytes_in_use, alloc_size);
991        assert!(usage.bytes_reserved >= alloc_size);
992    }
993
994    #[test_log::test]
995    fn noslice_alloc_allocs_new_storage() {
996        let mut memory_management = MemoryManagement::from_configuration(
997            BytesStorage::default(),
998            &DUMMY_MEM_PROPS,
999            MemoryConfiguration::Custom {
1000                pool_options: vec![MemoryPoolOptions {
1001                    pool_type: PoolType::ExclusivePages {
1002                        max_alloc_size: 1024,
1003                    },
1004                    dealloc_period: None,
1005                }],
1006            },
1007            Arc::new(ServerLogger::default()),
1008            options(),
1009        );
1010
1011        let alloc_size = 768;
1012        let _handle = memory_management.reserve(alloc_size);
1013        let _new_handle = memory_management.reserve(alloc_size);
1014        let usage = memory_management.memory_usage();
1015        assert_eq!(usage.number_allocs, 2);
1016        assert_eq!(usage.bytes_in_use, alloc_size * 2);
1017        assert!(usage.bytes_reserved >= alloc_size * 2);
1018    }
1019
1020    #[test_log::test]
1021    fn noslice_alloc_respects_alignment_size() {
1022        let mut memory_management = MemoryManagement::from_configuration(
1023            BytesStorage::default(),
1024            &MemoryDeviceProperties {
1025                max_page_size: DUMMY_MEM_PROPS.max_page_size,
1026                alignment: 50,
1027            },
1028            MemoryConfiguration::Custom {
1029                pool_options: vec![MemoryPoolOptions {
1030                    pool_type: PoolType::ExclusivePages {
1031                        max_alloc_size: 50 * 20,
1032                    },
1033                    dealloc_period: None,
1034                }],
1035            },
1036            Arc::new(ServerLogger::default()),
1037            options(),
1038        );
1039        let alloc_size = 40;
1040        let _handle = memory_management.reserve(alloc_size);
1041        let _new_handle = memory_management.reserve(alloc_size);
1042        let usage = memory_management.memory_usage();
1043        // Each slice should be aligned to 60 bytes, so 20 padding bytes.
1044        assert_eq!(usage.bytes_padding, 10 * 2);
1045    }
1046
1047    #[test_log::test]
1048    fn noslice_allocs_on_correct_page() {
1049        let pools = [100, 200, 300, 400]
1050            .iter()
1051            .map(|&size| MemoryPoolOptions {
1052                pool_type: PoolType::SlicedPages {
1053                    page_size: size,
1054                    max_slice_size: size,
1055                },
1056                dealloc_period: None,
1057            })
1058            .collect();
1059        let mut memory_management = MemoryManagement::from_configuration(
1060            BytesStorage::default(),
1061            &MemoryDeviceProperties {
1062                max_page_size: DUMMY_MEM_PROPS.max_page_size,
1063                alignment: 10,
1064            },
1065            MemoryConfiguration::Custom {
1066                pool_options: pools,
1067            },
1068            Arc::new(ServerLogger::default()),
1069            options(),
1070        );
1071        // Allocate one thing on each page.
1072        let alloc_sizes = [50, 150, 250, 350];
1073        let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1074        let usage = memory_management.memory_usage();
1075        // Total memory should be size of all pages, and no more.
1076        assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1077    }
1078
1079    #[test_log::test]
1080    fn noslice_allocate_deallocate_reallocate() {
1081        let mut memory_management = MemoryManagement::from_configuration(
1082            BytesStorage::default(),
1083            &MemoryDeviceProperties {
1084                max_page_size: 128 * 1024 * 1024,
1085                alignment: 32,
1086            },
1087            MemoryConfiguration::ExclusivePages,
1088            Arc::new(ServerLogger::default()),
1089            options(),
1090        );
1091        // Allocate a bunch
1092        let handles: Vec<_> = (0..5)
1093            .map(|i| memory_management.reserve(1000 * (i + 1)))
1094            .collect();
1095        let usage_before = memory_management.memory_usage();
1096        // Deallocate
1097        drop(handles);
1098        // Reallocate
1099        let _new_handles: Vec<_> = (0..5)
1100            .map(|i| memory_management.reserve(1000 * (i + 1)))
1101            .collect();
1102        let usage_after = memory_management.memory_usage();
1103        assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1104        assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1105        assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
1106    }
1107}