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}