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}