Skip to main content

ruda_runtime/runtime/memory_management/
memory_manage.rs

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