oxiblas_core/memory/
pool.rs

1//! Memory management utilities for OxiBLAS.
2//!
3//! This module provides:
4//! - Aligned memory allocation
5//! - Stack-based temporary allocation (StackReq pattern)
6//! - Cache-aware data layout utilities
7//! - Prefetch hints for cache optimization
8//! - Memory pool for temporary allocations
9//! - Custom allocator support via the `Alloc` trait
10
11use core::mem::size_of;
12
13use super::aligned_vec::AlignedVec;
14use super::alloc::*;
15
16// =============================================================================
17// MemoryPool - Pooled allocation for temporary buffers
18// =============================================================================
19
20/// A memory pool for temporary allocations.
21///
22/// This provides fast allocation for scratch space by reusing previously
23/// allocated buffers. Useful for algorithms that repeatedly allocate and
24/// free temporary storage of similar sizes.
25///
26/// # Thread Safety
27///
28/// This pool is NOT thread-safe. Use one pool per thread or wrap in a mutex.
29///
30/// # Example
31///
32/// ```
33/// use oxiblas_core::memory::MemoryPool;
34///
35/// let mut pool = MemoryPool::new();
36///
37/// // Acquire a buffer
38/// let buffer: Vec<f64> = pool.acquire(100);
39/// // ... use buffer ...
40///
41/// // Return buffer to pool for reuse
42/// pool.release(buffer);
43///
44/// // Next acquire may reuse the buffer
45/// let buffer2: Vec<f64> = pool.acquire(50);
46/// ```
47pub struct MemoryPool<T> {
48    /// Cached buffers, sorted by capacity (smallest first).
49    buffers: Vec<Vec<T>>,
50    /// Maximum number of buffers to keep.
51    max_cached: usize,
52    /// Maximum total bytes to keep cached.
53    max_bytes: usize,
54    /// Current cached bytes.
55    cached_bytes: usize,
56}
57
58impl<T> Default for MemoryPool<T> {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl<T> MemoryPool<T> {
65    /// Creates a new memory pool with default limits.
66    pub fn new() -> Self {
67        Self {
68            buffers: Vec::new(),
69            max_cached: 16,
70            max_bytes: 16 * 1024 * 1024, // 16 MB default
71            cached_bytes: 0,
72        }
73    }
74
75    /// Creates a new memory pool with custom limits.
76    ///
77    /// # Arguments
78    /// * `max_cached` - Maximum number of buffers to cache
79    /// * `max_bytes` - Maximum total bytes to cache
80    pub fn with_limits(max_cached: usize, max_bytes: usize) -> Self {
81        Self {
82            buffers: Vec::new(),
83            max_cached,
84            max_bytes,
85            cached_bytes: 0,
86        }
87    }
88
89    /// Acquires a buffer with at least the given capacity.
90    ///
91    /// If a suitable buffer exists in the pool, it is reused.
92    /// Otherwise, a new buffer is allocated.
93    pub fn acquire(&mut self, min_capacity: usize) -> Vec<T> {
94        // Find the smallest buffer that fits
95        let pos = self
96            .buffers
97            .iter()
98            .position(|b| b.capacity() >= min_capacity);
99
100        if let Some(idx) = pos {
101            let mut buffer = self.buffers.remove(idx);
102            self.cached_bytes -= buffer.capacity() * size_of::<T>();
103            buffer.clear();
104            buffer
105        } else {
106            Vec::with_capacity(min_capacity)
107        }
108    }
109
110    /// Returns a buffer to the pool for future reuse.
111    ///
112    /// The buffer is cleared before being stored.
113    pub fn release(&mut self, mut buffer: Vec<T>) {
114        let bytes = buffer.capacity() * size_of::<T>();
115
116        // Check if we can cache this buffer
117        if self.buffers.len() >= self.max_cached {
118            // Pool is full, just drop the buffer
119            return;
120        }
121
122        if self.cached_bytes + bytes > self.max_bytes {
123            // Would exceed byte limit, drop the buffer
124            return;
125        }
126
127        buffer.clear();
128        self.cached_bytes += bytes;
129
130        // Insert in sorted order by capacity
131        let pos = self
132            .buffers
133            .iter()
134            .position(|b| b.capacity() >= buffer.capacity())
135            .unwrap_or(self.buffers.len());
136
137        self.buffers.insert(pos, buffer);
138    }
139
140    /// Returns the number of cached buffers.
141    pub fn cached_count(&self) -> usize {
142        self.buffers.len()
143    }
144
145    /// Returns the total cached bytes.
146    pub fn cached_bytes(&self) -> usize {
147        self.cached_bytes
148    }
149
150    /// Clears all cached buffers.
151    pub fn clear(&mut self) {
152        self.buffers.clear();
153        self.cached_bytes = 0;
154    }
155
156    /// Shrinks the pool to fit within the current limits.
157    ///
158    /// Removes the largest buffers first to stay within limits.
159    pub fn shrink_to_limits(&mut self) {
160        while self.buffers.len() > self.max_cached || self.cached_bytes > self.max_bytes {
161            if let Some(buffer) = self.buffers.pop() {
162                self.cached_bytes -= buffer.capacity() * size_of::<T>();
163            } else {
164                break;
165            }
166        }
167    }
168}
169
170/// A typed memory pool for aligned allocations.
171///
172/// Unlike `MemoryPool`, this uses `AlignedVec` for SIMD-friendly allocations.
173pub struct AlignedPool<T, const ALIGN: usize = DEFAULT_ALIGN> {
174    /// Cached buffers.
175    buffers: Vec<AlignedVec<T, ALIGN>>,
176    /// Maximum number of buffers to keep.
177    max_cached: usize,
178}
179
180impl<T: Clone, const ALIGN: usize> Default for AlignedPool<T, ALIGN> {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl<T: Clone, const ALIGN: usize> AlignedPool<T, ALIGN> {
187    /// Creates a new aligned memory pool.
188    pub fn new() -> Self {
189        Self {
190            buffers: Vec::new(),
191            max_cached: 8,
192        }
193    }
194
195    /// Creates a pool with a custom cache limit.
196    pub fn with_limit(max_cached: usize) -> Self {
197        Self {
198            buffers: Vec::new(),
199            max_cached,
200        }
201    }
202
203    /// Acquires an aligned buffer with at least the given capacity.
204    pub fn acquire(&mut self, min_capacity: usize) -> AlignedVec<T, ALIGN> {
205        // Find a suitable buffer
206        let pos = self
207            .buffers
208            .iter()
209            .position(|b| b.capacity() >= min_capacity);
210
211        if let Some(idx) = pos {
212            let mut buffer = self.buffers.remove(idx);
213            buffer.clear();
214            buffer
215        } else {
216            AlignedVec::with_capacity(min_capacity)
217        }
218    }
219
220    /// Returns an aligned buffer to the pool.
221    pub fn release(&mut self, mut buffer: AlignedVec<T, ALIGN>) {
222        if self.buffers.len() >= self.max_cached {
223            // Pool is full
224            return;
225        }
226
227        buffer.clear();
228
229        // Insert in sorted order by capacity
230        let pos = self
231            .buffers
232            .iter()
233            .position(|b| b.capacity() >= buffer.capacity())
234            .unwrap_or(self.buffers.len());
235
236        self.buffers.insert(pos, buffer);
237    }
238
239    /// Clears all cached buffers.
240    pub fn clear(&mut self) {
241        self.buffers.clear();
242    }
243}