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    /// Compact adaptive allocations into already held pages, then release empty pages.
411    /// Pinned resources stay in place. Fixed pools only release empty pages.
412    /// Relocation errors remain explicit; success does not guarantee pages were freed.
413    pub fn compact(&mut self) -> Result<(), IoError> {
414        if let Some(adaptive) = self.adaptive.as_mut() {
415            adaptive.compact(&mut self.pools, &mut self.storage)?;
416        }
417        self.cleanup(true);
418        Ok(())
419    }
420
421    /// Returns the storage from the specified binding
422    pub fn get_cursor(&self, binding: ManagedMemoryBinding) -> Result<u64, IoError> {
423        let slice = self.find(binding)?;
424        Ok(slice.cursor)
425    }
426
427    /// Returns the storage from the specified binding
428    fn find(&self, binding: ManagedMemoryBinding) -> Result<&Slice, IoError> {
429        let location = binding.descriptor().location();
430
431        if location.pool >= self.pools.len() as u8 {
432            return self.persistent.find_at(location);
433        }
434
435        let pool =
436            self.pools
437                .get(location.pool as usize)
438                .ok_or_else(|| IoError::NotFound {
439                    backtrace: BackTrace::capture(),
440                    reason: format!("Pool {} doesn't exist", location.pool).into(),
441                })?;
442
443        let slice = pool.find_at(location)?;
444
445        assert_eq!(slice.handle.descriptor(), binding.descriptor());
446
447        Ok(slice)
448    }
449
450    /// Returns the storage from the specified binding
451    pub fn get_storage(&mut self, binding: ManagedMemoryBinding) -> Result<StorageHandle, IoError> {
452        let slice = self.find(binding)?;
453        Ok(slice.storage.clone())
454    }
455
456    /// Returns the resource from the storage at the specified handle
457    pub fn get_resource(
458        &mut self,
459        binding: ManagedMemoryBinding,
460        offset_start: Option<u64>,
461        offset_end: Option<u64>,
462    ) -> Result<Storage::Resource, IoError> {
463        let pin_binding = self.adaptive.as_ref().map(|_| binding.clone());
464        let handle = self.get_storage(binding)?;
465
466        let handle = match offset_start {
467            Some(offset) => handle.offset_start(offset),
468            None => handle,
469        };
470        let handle = match offset_end {
471            Some(offset) => handle.offset_end(offset),
472            None => handle,
473        };
474        if let Some(binding) = pin_binding {
475            Ok(self.storage().get_pinned(&handle, binding))
476        } else {
477            Ok(self.storage().get(&handle))
478        }
479    }
480
481    /// Whether an adaptive reservation may relocate existing data. Queued-command
482    /// backends must submit their pending encoders before calling `reserve` here.
483    pub fn relocation_pending(&self, size: u64) -> bool {
484        self.adaptive.as_ref().is_some_and(|state| state.pending(&self.pools, size))
485    }
486
487    /// Finds a spot in memory for a resource with the given size in bytes, and returns a handle to it
488    #[cfg_attr(feature = "runtime-tracing", tracing::instrument(level = "trace", skip(self)))]
489    pub fn reserve(&mut self, size: u64) -> Result<ManagedMemoryHandle, IoError> {
490        // If this happens every nanosecond, counts overflows after 585 years, so not worth thinking too
491        // hard about overflow here.
492        self.alloc_reserve_count += 1;
493
494        if let Some(val) = self.persistent.try_reserve(size) {
495            self.logger.log_memory(
496                |level| matches!(level, MemoryLogLevel::Full),
497                || {
498                    format!(
499                        "[{}] Reserved memory {size} using persistent memory",
500                        self.name
501                    )
502                },
503            );
504            return Ok(val);
505        }
506
507        if matches!(self.mode, MemoryAllocationMode::Persistent) || self.persistent.has_size(size) {
508            let allocated = self.persistent.alloc(&mut self.storage, size);
509
510            self.logger.log_memory(
511                |level| !matches!(level, MemoryLogLevel::Disabled),
512                || {
513                    format!(
514                        "[{}] Allocated a new memory page using persistent memory, \n{}",
515                        self.name, self,
516                    )
517                },
518            );
519            return allocated;
520        }
521
522        self.logger.log_memory(
523            |level| matches!(level, MemoryLogLevel::Full),
524            || {
525                format!(
526                    "[{}] Reserved memory {} using dynamic pool",
527                    self.name,
528                    BytesFormat::new(size)
529                )
530            },
531        );
532
533        if let Some(adaptive) = &mut self.adaptive {
534            return adaptive.reserve(&mut self.pools, &mut self.storage, size);
535        }
536
537        // Find first pool that fits this allocation
538        let pool = self
539            .pools
540            .iter_mut()
541            .find(|p| p.accept(size))
542            .ok_or(IoError::BufferTooBig {
543                size,
544                backtrace: BackTrace::capture(),
545            })?;
546
547        if let Some(slice) = pool.try_reserve(size) {
548            return Ok(slice);
549        }
550
551        let allocated = pool.alloc(&mut self.storage, size);
552
553        self.logger.log_memory(
554            |level| matches!(level, MemoryLogLevel::Full),
555            || {
556                format!(
557                    "[{}], Allocated a new memory page, current usage: \n{}",
558                    self.name, self
559                )
560            },
561        );
562
563        allocated
564    }
565
566    /// Fetch the storage used by the memory manager.
567    ///
568    /// # Notes
569    ///
570    /// The storage should probably not be used for allocations since the handles won't be
571    /// compatible with the ones provided by the current trait. Prefer using the
572    /// [alloc](ComputeStorage::alloc) and [dealloc](ComputeStorage::dealloc) functions.
573    ///
574    /// This is useful if you need to time the deallocations based on async computation, or to
575    /// change the mode of storage for different reasons.
576    pub fn storage(&mut self) -> &mut Storage {
577        &mut self.storage
578    }
579
580    /// Get the current memory usage.
581    pub fn memory_usage(&self) -> MemoryUsage {
582        let memory_usage = self.pools.iter().map(|x| x.get_memory_usage()).fold(
583            MemoryUsage {
584                number_allocs: 0,
585                bytes_in_use: 0,
586                bytes_padding: 0,
587                bytes_reserved: 0,
588            },
589            |m1, m2| m1.combine(m2),
590        );
591        memory_usage.combine(self.persistent.get_memory_usage())
592    }
593
594    /// Print out a report of the current memory usage.
595    pub fn print_memory_usage(&self) {
596        #[cfg(feature = "runtime-std")]
597        log::info!("{}", self.memory_usage());
598    }
599
600    /// Binds the given [handle](HandleId) to a [`MemorySlot`].
601    pub fn bind(
602        &mut self,
603        reserved: ManagedMemoryHandle,
604        assigned: ManagedMemoryHandle,
605        cursor: u64,
606    ) -> Result<(), IoError> {
607        let descriptor = reserved.descriptor();
608
609        if descriptor.location().init == 0 {
610            return Err(IoError::NotFound {
611                backtrace: BackTrace::capture(),
612                reason: "Reserved memory isn't initialized".into(),
613            });
614        }
615
616        let pool_index = descriptor.location().pool as usize;
617        if pool_index >= self.pools.len() {
618            return self.persistent.bind(reserved, assigned, cursor);
619        }
620
621        self.pools
622            .get_mut(pool_index)
623            .map(|p| p.bind(reserved, assigned, cursor))
624            .ok_or_else(|| IoError::NotFound {
625                backtrace: BackTrace::capture(),
626                reason: format!("Memory pool {} doesn't exist", pool_index).into(),
627            })?
628    }
629}
630
631impl<Storage: ComputeStorage> core::fmt::Display for MemoryManagement<Storage> {
632    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
633        f.write_str("\n# MemoryManagement\n\n")?;
634        f.write_fmt(format_args!(" - name: {:?}\n", self.name))?;
635        f.write_fmt(format_args!("\n## Persistent\n\n{}", self.persistent))?;
636        f.write_str("\n## Dynamic\n\n")?;
637
638        for pool in self.pools.iter() {
639            match pool {
640                DynamicPool::Sliced(pool) => f.write_fmt(format_args!("{pool}\n"))?,
641                DynamicPool::Exclusive(pool) => f.write_fmt(format_args!("{pool}\n"))?,
642            }
643        }
644        let memory_usage = self.memory_usage();
645        f.write_fmt(format_args!("\n## Summary\n\n{memory_usage}"))?;
646
647        Ok(())
648    }
649}
650
651impl<Storage> core::fmt::Debug for MemoryManagement<Storage> {
652    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
653        f.write_str(
654            alloc::format!(
655                "DynamicMemoryManagement {:?}",
656                core::any::type_name::<Storage>(),
657            )
658            .as_str(),
659        )
660    }
661}
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666    use crate::runtime::{memory_management::MemoryManagement, storage::BytesStorage};
667    use alloc::vec;
668
669    const DUMMY_MEM_PROPS: MemoryDeviceProperties = MemoryDeviceProperties {
670        max_page_size: 128 * 1024 * 1024,
671        alignment: 32,
672    };
673
674    fn options() -> MemoryManagementOptions {
675        MemoryManagementOptions {
676            name: "test".into(),
677            memory: MemoryAllocationOption::FromConfig,
678        }
679    }
680
681    // Test pools with slices.
682    #[test_log::test]
683    #[cfg(not(exclusive_memory_only))]
684    fn test_handle_mutability() {
685        let mut memory_management = MemoryManagement::from_configuration(
686            BytesStorage::default(),
687            &DUMMY_MEM_PROPS,
688            MemoryConfiguration::SubSlices,
689            Arc::new(ServerLogger::default()),
690            options(),
691        );
692        let handle = memory_management.reserve(10).unwrap();
693        let other_ref = handle.clone();
694        assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
695        drop(other_ref);
696        assert!(handle.can_mut(), "Handle should be mut when only one ref.");
697    }
698
699    // Test pools with slices.
700    #[test_log::test]
701    #[cfg(not(exclusive_memory_only))]
702    fn test_memory_usage() {
703        let max_page_size = 512;
704
705        let mut memory_management = MemoryManagement::from_configuration(
706            BytesStorage::default(),
707            &DUMMY_MEM_PROPS,
708            MemoryConfiguration::Custom {
709                pool_options: vec![MemoryPoolOptions {
710                    pool_type: PoolType::ExclusivePages {
711                        max_alloc_size: max_page_size,
712                    },
713                    dealloc_period: None,
714                }],
715            },
716            Arc::new(ServerLogger::default()),
717            options(),
718        );
719        let handle = memory_management.reserve(100);
720        let usage = memory_management.memory_usage();
721
722        assert_eq!(usage.bytes_in_use, 100);
723        assert!(usage.bytes_reserved >= 100 && usage.bytes_reserved <= max_page_size);
724
725        // Drop and re-alloc.
726        drop(handle);
727        let _handle = memory_management.reserve(100);
728        let usage_new = memory_management.memory_usage();
729        assert_eq!(usage, usage_new);
730    }
731
732    #[test_log::test]
733    fn alloc_two_chunks_on_one_page() {
734        let page_size = 2048;
735
736        let mut memory_management = MemoryManagement::from_configuration(
737            BytesStorage::default(),
738            &DUMMY_MEM_PROPS,
739            MemoryConfiguration::Custom {
740                pool_options: vec![MemoryPoolOptions {
741                    pool_type: PoolType::SlicedPages {
742                        page_size,
743                        max_slice_size: page_size,
744                    },
745                    dealloc_period: None,
746                }],
747            },
748            Arc::new(ServerLogger::default()),
749            options(),
750        );
751
752        let alloc_size = 512;
753        let _handle = memory_management.reserve(alloc_size);
754        let _new_handle = memory_management.reserve(alloc_size);
755
756        let usage = memory_management.memory_usage();
757        assert_eq!(usage.number_allocs, 2);
758        assert_eq!(usage.bytes_in_use, alloc_size * 2);
759        assert_eq!(usage.bytes_reserved, page_size);
760    }
761
762    #[test_log::test]
763    fn alloc_reuses_storage() {
764        // If no storage is re-used, this will allocate two pages.
765        let page_size = 512;
766
767        let mut memory_management = MemoryManagement::from_configuration(
768            BytesStorage::default(),
769            &DUMMY_MEM_PROPS,
770            MemoryConfiguration::Custom {
771                pool_options: vec![MemoryPoolOptions {
772                    pool_type: PoolType::SlicedPages {
773                        page_size,
774                        max_slice_size: page_size,
775                    },
776                    dealloc_period: None,
777                }],
778            },
779            Arc::new(ServerLogger::default()),
780            options(),
781        );
782
783        let alloc_size = 512;
784        let _handle = memory_management.reserve(alloc_size);
785        drop(_handle);
786        let _new_handle = memory_management.reserve(alloc_size);
787
788        let usage = memory_management.memory_usage();
789        assert_eq!(usage.number_allocs, 1);
790        assert_eq!(usage.bytes_in_use, alloc_size);
791        assert_eq!(usage.bytes_reserved, page_size);
792    }
793
794    #[test_log::test]
795    fn alloc_allocs_new_storage() {
796        let page_size = 1024;
797
798        let mut memory_management = MemoryManagement::from_configuration(
799            BytesStorage::default(),
800            &DUMMY_MEM_PROPS,
801            MemoryConfiguration::Custom {
802                pool_options: vec![MemoryPoolOptions {
803                    pool_type: PoolType::SlicedPages {
804                        page_size,
805                        max_slice_size: page_size,
806                    },
807                    dealloc_period: None,
808                }],
809            },
810            Arc::new(ServerLogger::default()),
811            options(),
812        );
813
814        let alloc_size = 768;
815        let _handle = memory_management.reserve(alloc_size);
816        let _new_handle = memory_management.reserve(alloc_size);
817
818        let usage = memory_management.memory_usage();
819        assert_eq!(usage.number_allocs, 2);
820        assert_eq!(usage.bytes_in_use, alloc_size * 2);
821        assert_eq!(usage.bytes_reserved, page_size * 2);
822    }
823
824    #[test_log::test]
825    fn alloc_respects_alignment_size() {
826        let page_size = 500;
827        let mut memory_management = MemoryManagement::from_configuration(
828            BytesStorage::default(),
829            &MemoryDeviceProperties {
830                max_page_size: page_size,
831                alignment: 50,
832            },
833            MemoryConfiguration::Custom {
834                pool_options: vec![MemoryPoolOptions {
835                    pool_type: PoolType::SlicedPages {
836                        page_size,
837                        max_slice_size: page_size,
838                    },
839                    dealloc_period: None,
840                }],
841            },
842            Arc::new(ServerLogger::default()),
843            options(),
844        );
845        let alloc_size = 40;
846        let _handle = memory_management.reserve(alloc_size);
847        let _new_handle = memory_management.reserve(alloc_size);
848        let usage = memory_management.memory_usage();
849        // Each slice should be aligned to 50 bytes, so 20 padding bytes.
850        assert_eq!(usage.bytes_padding, 10 * 2);
851    }
852
853    #[test_log::test]
854    fn allocs_on_correct_page() {
855        let sizes = [100, 200, 300, 400];
856
857        let pools = sizes
858            .iter()
859            .map(|size| MemoryPoolOptions {
860                pool_type: PoolType::SlicedPages {
861                    page_size: *size,
862                    max_slice_size: *size,
863                },
864                dealloc_period: None,
865            })
866            .collect();
867        let mut memory_management = MemoryManagement::from_configuration(
868            BytesStorage::default(),
869            &MemoryDeviceProperties {
870                max_page_size: 128 * 1024 * 1024,
871                alignment: 10,
872            },
873            MemoryConfiguration::Custom {
874                pool_options: pools,
875            },
876            Arc::new(ServerLogger::default()),
877            options(),
878        );
879        // Allocate one thing on each page.
880        let alloc_sizes = [50, 150, 250, 350];
881        let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
882
883        let usage = memory_management.memory_usage();
884
885        // Total memory should be size of all pages, and no more.
886        assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
887        assert!(usage.bytes_reserved >= sizes.iter().sum::<u64>());
888    }
889
890    #[test_log::test]
891    #[cfg(not(exclusive_memory_only))]
892    fn allocate_deallocate_reallocate() {
893        let mut memory_management = MemoryManagement::from_configuration(
894            BytesStorage::default(),
895            &MemoryDeviceProperties {
896                max_page_size: 128 * 1024 * 1024,
897                alignment: 32,
898            },
899            MemoryConfiguration::SubSlices,
900            Arc::new(ServerLogger::default()),
901            options(),
902        );
903        // Allocate a bunch
904        let handles: Vec<_> = (0..5)
905            .map(|i| memory_management.reserve(1000 * (i + 1)))
906            .collect();
907        let usage_before = memory_management.memory_usage();
908        // Deallocate
909        drop(handles);
910        // Reallocate
911        let _new_handles: Vec<_> = (0..5)
912            .map(|i| memory_management.reserve(1000 * (i + 1)))
913            .collect();
914        let usage_after = memory_management.memory_usage();
915        assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
916        assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
917        // Usage after can actually be _less_ because of defragging.
918        assert!(usage_before.bytes_reserved >= usage_after.bytes_reserved);
919    }
920
921    #[test_log::test]
922    #[cfg(not(exclusive_memory_only))]
923    fn test_fragmentation_resistance() {
924        let mut memory_management = MemoryManagement::from_configuration(
925            BytesStorage::default(),
926            &MemoryDeviceProperties {
927                max_page_size: 128 * 1024 * 1024,
928                alignment: 32,
929            },
930            MemoryConfiguration::SubSlices,
931            Arc::new(ServerLogger::default()),
932            options(),
933        );
934        // Allocate a mix of small and large chunks
935        let sizes = [50, 1000, 100, 5000, 200, 10000, 300];
936        let handles: Vec<_> = sizes
937            .iter()
938            .map(|&size| memory_management.reserve(size).unwrap())
939            .collect();
940        let usage_before = memory_management.memory_usage();
941        // Deallocate every other allocation
942        for i in (0..handles.len()).step_by(2) {
943            drop(handles[i].clone());
944        }
945        // Reallocate similar sizes
946        for &size in &sizes[0..sizes.len() / 2] {
947            memory_management.reserve(size).unwrap();
948        }
949        let usage_after = memory_management.memory_usage();
950        // Check that we haven't increased our memory usage significantly
951        assert!(usage_after.bytes_reserved <= (usage_before.bytes_reserved as f64 * 1.1) as u64);
952    }
953
954    // Test pools without slices. More or less same as tests above.
955    #[test_log::test]
956    fn noslice_test_handle_mutability() {
957        let mut memory_management = MemoryManagement::from_configuration(
958            BytesStorage::default(),
959            &(MemoryDeviceProperties {
960                max_page_size: 128 * 1024 * 1024,
961                alignment: 32,
962            }),
963            MemoryConfiguration::ExclusivePages,
964            Arc::new(ServerLogger::default()),
965            options(),
966        );
967        let handle = memory_management.reserve(10).unwrap();
968        let other_ref = handle.clone();
969        assert!(!handle.can_mut(), "Handle can't be mut when multiple ref.");
970        drop(other_ref);
971        assert!(handle.can_mut(), "Handle should be mut when only one ref.");
972    }
973
974    #[test_log::test]
975    fn noslice_alloc_two_chunk() {
976        let mut memory_management = MemoryManagement::from_configuration(
977            BytesStorage::default(),
978            &DUMMY_MEM_PROPS,
979            MemoryConfiguration::Custom {
980                pool_options: vec![MemoryPoolOptions {
981                    pool_type: PoolType::ExclusivePages {
982                        max_alloc_size: 1024,
983                    },
984                    dealloc_period: None,
985                }],
986            },
987            Arc::new(ServerLogger::default()),
988            options(),
989        );
990
991        let alloc_size = 512;
992        let _handle = memory_management.reserve(alloc_size);
993        let _new_handle = memory_management.reserve(alloc_size);
994
995        let usage = memory_management.memory_usage();
996        assert_eq!(usage.number_allocs, 2);
997        assert_eq!(usage.bytes_in_use, alloc_size * 2);
998        assert!(usage.bytes_reserved >= alloc_size * 2);
999    }
1000
1001    #[test_log::test]
1002    fn noslice_alloc_reuses_storage() {
1003        // If no storage is re-used, this will allocate two pages.
1004        let mut memory_management = MemoryManagement::from_configuration(
1005            BytesStorage::default(),
1006            &DUMMY_MEM_PROPS,
1007            MemoryConfiguration::Custom {
1008                pool_options: vec![MemoryPoolOptions {
1009                    pool_type: PoolType::ExclusivePages {
1010                        max_alloc_size: 1024,
1011                    },
1012                    dealloc_period: None,
1013                }],
1014            },
1015            Arc::new(ServerLogger::default()),
1016            options(),
1017        );
1018
1019        let alloc_size = 512;
1020        let _handle = memory_management.reserve(alloc_size);
1021        drop(_handle);
1022        let _new_handle = memory_management.reserve(alloc_size);
1023
1024        let usage = memory_management.memory_usage();
1025        assert_eq!(usage.number_allocs, 1);
1026        assert_eq!(usage.bytes_in_use, alloc_size);
1027        assert!(usage.bytes_reserved >= alloc_size);
1028    }
1029
1030    #[test_log::test]
1031    fn noslice_alloc_allocs_new_storage() {
1032        let mut memory_management = MemoryManagement::from_configuration(
1033            BytesStorage::default(),
1034            &DUMMY_MEM_PROPS,
1035            MemoryConfiguration::Custom {
1036                pool_options: vec![MemoryPoolOptions {
1037                    pool_type: PoolType::ExclusivePages {
1038                        max_alloc_size: 1024,
1039                    },
1040                    dealloc_period: None,
1041                }],
1042            },
1043            Arc::new(ServerLogger::default()),
1044            options(),
1045        );
1046
1047        let alloc_size = 768;
1048        let _handle = memory_management.reserve(alloc_size);
1049        let _new_handle = memory_management.reserve(alloc_size);
1050        let usage = memory_management.memory_usage();
1051        assert_eq!(usage.number_allocs, 2);
1052        assert_eq!(usage.bytes_in_use, alloc_size * 2);
1053        assert!(usage.bytes_reserved >= alloc_size * 2);
1054    }
1055
1056    #[test_log::test]
1057    fn noslice_alloc_respects_alignment_size() {
1058        let mut memory_management = MemoryManagement::from_configuration(
1059            BytesStorage::default(),
1060            &MemoryDeviceProperties {
1061                max_page_size: DUMMY_MEM_PROPS.max_page_size,
1062                alignment: 50,
1063            },
1064            MemoryConfiguration::Custom {
1065                pool_options: vec![MemoryPoolOptions {
1066                    pool_type: PoolType::ExclusivePages {
1067                        max_alloc_size: 50 * 20,
1068                    },
1069                    dealloc_period: None,
1070                }],
1071            },
1072            Arc::new(ServerLogger::default()),
1073            options(),
1074        );
1075        let alloc_size = 40;
1076        let _handle = memory_management.reserve(alloc_size);
1077        let _new_handle = memory_management.reserve(alloc_size);
1078        let usage = memory_management.memory_usage();
1079        // Each slice should be aligned to 60 bytes, so 20 padding bytes.
1080        assert_eq!(usage.bytes_padding, 10 * 2);
1081    }
1082
1083    #[test_log::test]
1084    fn noslice_allocs_on_correct_page() {
1085        let pools = [100, 200, 300, 400]
1086            .iter()
1087            .map(|&size| MemoryPoolOptions {
1088                pool_type: PoolType::SlicedPages {
1089                    page_size: size,
1090                    max_slice_size: size,
1091                },
1092                dealloc_period: None,
1093            })
1094            .collect();
1095        let mut memory_management = MemoryManagement::from_configuration(
1096            BytesStorage::default(),
1097            &MemoryDeviceProperties {
1098                max_page_size: DUMMY_MEM_PROPS.max_page_size,
1099                alignment: 10,
1100            },
1101            MemoryConfiguration::Custom {
1102                pool_options: pools,
1103            },
1104            Arc::new(ServerLogger::default()),
1105            options(),
1106        );
1107        // Allocate one thing on each page.
1108        let alloc_sizes = [50, 150, 250, 350];
1109        let _handles = alloc_sizes.map(|s| memory_management.reserve(s));
1110        let usage = memory_management.memory_usage();
1111        // Total memory should be size of all pages, and no more.
1112        assert_eq!(usage.bytes_in_use, alloc_sizes.iter().sum::<u64>());
1113    }
1114
1115    #[test_log::test]
1116    fn noslice_allocate_deallocate_reallocate() {
1117        let mut memory_management = MemoryManagement::from_configuration(
1118            BytesStorage::default(),
1119            &MemoryDeviceProperties {
1120                max_page_size: 128 * 1024 * 1024,
1121                alignment: 32,
1122            },
1123            MemoryConfiguration::ExclusivePages,
1124            Arc::new(ServerLogger::default()),
1125            options(),
1126        );
1127        // Allocate a bunch
1128        let handles: Vec<_> = (0..5)
1129            .map(|i| memory_management.reserve(1000 * (i + 1)))
1130            .collect();
1131        let usage_before = memory_management.memory_usage();
1132        // Deallocate
1133        drop(handles);
1134        // Reallocate
1135        let _new_handles: Vec<_> = (0..5)
1136            .map(|i| memory_management.reserve(1000 * (i + 1)))
1137            .collect();
1138        let usage_after = memory_management.memory_usage();
1139        assert_eq!(usage_before.number_allocs, usage_after.number_allocs);
1140        assert_eq!(usage_before.bytes_in_use, usage_after.bytes_in_use);
1141        assert_eq!(usage_before.bytes_reserved, usage_after.bytes_reserved);
1142    }
1143}