Skip to main content

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