scirs2_linalg/gpu/
memory.rs1use super::{GpuBuffer, GpuContext, GpuContextAlloc};
4use crate::error::LinalgResult;
5
6#[derive(Debug, Clone, Copy)]
8pub enum MemoryStrategy {
9 OnDemand,
11 Pooled,
13 Unified,
15 Pinned,
17}
18
19pub struct MemoryPool<T> {
21 buffers: Vec<Box<dyn GpuBuffer<T>>>,
22 max_poolsize: usize,
23 total_allocated: usize,
24}
25
26impl<T: Clone + Send + Sync + Copy + 'static + std::fmt::Debug> MemoryPool<T> {
27 pub fn new(max_poolsize: usize) -> Self {
29 Self {
30 buffers: Vec::new(),
31 max_poolsize,
32 total_allocated: 0,
33 }
34 }
35
36 pub fn get_buffer<C: GpuContextAlloc>(
38 &mut self,
39 context: &C,
40 size: usize,
41 ) -> LinalgResult<Box<dyn GpuBuffer<T>>> {
42 for i in 0..self.buffers.len() {
44 if self.buffers[i].len() >= size {
45 return Ok(self.buffers.swap_remove(i));
46 }
47 }
48
49 let buffer = context.allocate_buffer(size)?;
51 self.total_allocated += size;
52 Ok(buffer)
53 }
54
55 pub fn return_buffer(&mut self, buffer: Box<dyn GpuBuffer<T>>) {
57 if self.buffers.len() < self.max_poolsize {
58 self.buffers.push(buffer);
59 }
60 }
62
63 pub fn clear(&mut self) {
65 self.buffers.clear();
66 self.total_allocated = 0;
67 }
68
69 pub fn total_allocated(&self) -> usize {
71 self.total_allocated
72 }
73
74 pub fn poolsize(&self) -> usize {
76 self.buffers.len()
77 }
78}
79
80pub trait MemoryTransfer<T> {
82 fn copy_host_to_device_async(
84 &self,
85 host_data: &[T],
86 device_buffer: &mut dyn GpuBuffer<T>,
87 ) -> LinalgResult<()>;
88
89 fn copy_device_to_host_async(
91 &self,
92 device_buffer: &dyn GpuBuffer<T>,
93 host_data: &mut [T],
94 ) -> LinalgResult<()>;
95
96 fn copy_device_to_device(
98 &self,
99 src_buffer: &dyn GpuBuffer<T>,
100 dst_buffer: &mut dyn GpuBuffer<T>,
101 ) -> LinalgResult<()>;
102}
103
104pub struct MemoryBandwidthProfiler {
106 measurements: Vec<f64>, }
108
109impl Default for MemoryBandwidthProfiler {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl MemoryBandwidthProfiler {
116 pub fn new() -> Self {
118 Self {
119 measurements: Vec::new(),
120 }
121 }
122
123 pub fn measure_bandwidth<T, C: GpuContextAlloc>(
125 &mut self,
126 context: &C,
127 transfersize: usize,
128 ) -> LinalgResult<f64>
129 where
130 T: Clone + Send + Sync + Default + Copy + 'static + std::fmt::Debug,
131 {
132 let _start_time = std::time::Instant::now();
133
134 let mut buffer1 = context.allocate_buffer::<T>(transfersize)?;
136 let _buffer2 = context.allocate_buffer::<T>(transfersize)?;
137
138 let test_data: Vec<T> = (0..transfersize).map(|_| T::default()).collect();
140
141 let h2d_start = std::time::Instant::now();
143 buffer1.copy_from_host(&test_data)?;
144 context.synchronize()?;
145 let h2d_time = h2d_start.elapsed().as_secs_f64();
146
147 let mut result_data = vec![T::default(); transfersize];
149 let d2h_start = std::time::Instant::now();
150 buffer1.copy_to_host(&mut result_data)?;
151 context.synchronize()?;
152 let d2h_time = d2h_start.elapsed().as_secs_f64();
153
154 let bytes_transferred = transfersize * std::mem::size_of::<T>();
156 let total_time = h2d_time + d2h_time;
157 let bandwidth_gb_s = (bytes_transferred as f64 * 2.0) / (total_time * 1e9);
158
159 self.measurements.push(bandwidth_gb_s);
160 Ok(bandwidth_gb_s)
161 }
162
163 pub fn average_bandwidth(&self) -> f64 {
165 if self.measurements.is_empty() {
166 0.0
167 } else {
168 self.measurements.iter().sum::<f64>() / self.measurements.len() as f64
169 }
170 }
171
172 pub fn peak_bandwidth(&self) -> f64 {
174 self.measurements.iter().copied().fold(0.0, f64::max)
175 }
176}
177
178pub struct MemoryOptimizer {
180 usage_history: Vec<usize>,
181 peak_usage: usize,
182}
183
184impl Default for MemoryOptimizer {
185 fn default() -> Self {
186 Self::new()
187 }
188}
189
190impl MemoryOptimizer {
191 pub fn new() -> Self {
193 Self {
194 usage_history: Vec::new(),
195 peak_usage: 0,
196 }
197 }
198
199 pub fn record_usage(&mut self, usagebytes: usize) {
201 self.usage_history.push(usagebytes);
202 self.peak_usage = self.peak_usage.max(usagebytes);
203 }
204
205 pub fn get_suggestions(&self, devicememory: usize) -> Vec<String> {
207 let mut suggestions = Vec::new();
208
209 if self.peak_usage > devicememory / 2 {
210 suggestions
211 .push("Consider using _memory pooling to reduce allocation overhead".to_string());
212 }
213
214 if self.usage_history.len() > 10 {
215 let recent_usage: Vec<_> = self.usage_history.iter().rev().take(10).collect();
216 let avg_recent = recent_usage.iter().copied().sum::<usize>() / recent_usage.len();
217
218 if avg_recent < self.peak_usage / 4 {
219 suggestions.push(
220 "Memory usage varies significantly - consider dynamic allocation".to_string(),
221 );
222 }
223 }
224
225 if self.peak_usage > devicememory * 3 / 4 {
226 suggestions
227 .push("High _memory usage detected - consider out-of-core algorithms".to_string());
228 }
229
230 suggestions
231 }
232
233 pub fn efficiency_score(&self, devicememory: usize) -> f64 {
235 if self.usage_history.is_empty() {
236 return 100.0;
237 }
238
239 let avg_usage = self.usage_history.iter().sum::<usize>() / self.usage_history.len();
240 let utilization = avg_usage as f64 / devicememory as f64;
241
242 if utilization < 0.3 {
244 (utilization / 0.3) * 50.0
245 } else if utilization <= 0.7 {
246 100.0
247 } else if utilization <= 1.0 {
248 100.0 - ((utilization - 0.7) / 0.3) * 50.0
249 } else {
250 0.0 }
252 }
253}
254
255#[allow(dead_code)]
257pub fn check_memory_requirements(
258 context: &dyn GpuContext,
259 matricessizes: &[(usize, usize)],
260 elementsize: usize,
261) -> LinalgResult<bool> {
262 let total_elements: usize = matricessizes.iter().map(|(rows, cols)| rows * cols).sum();
263
264 let total_bytes = total_elements * elementsize;
265 let available_memory = context.available_memory()?;
266
267 Ok(total_bytes < available_memory / 2)
269}
270
271#[allow(dead_code)]
273pub fn suggest_memory_strategy(
274 problemsize: usize,
275 available_memory: usize,
276 unified_memory_available: bool,
277) -> MemoryStrategy {
278 let memory_ratio = problemsize as f64 / available_memory as f64;
279
280 if memory_ratio > 0.8 {
281 if unified_memory_available {
283 MemoryStrategy::Unified
284 } else {
285 MemoryStrategy::OnDemand
286 }
287 } else if memory_ratio < 0.1 {
288 MemoryStrategy::Pinned
290 } else {
291 MemoryStrategy::Pooled
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::gpu::backends::CpuFallbackBackend;
300 use crate::gpu::GpuBackend;
301
302 #[test]
303 fn test_memory_pool_operations() {
304 let mut pool = MemoryPool::<f32>::new(5);
305 assert_eq!(pool.poolsize(), 0);
306 assert_eq!(pool.total_allocated(), 0);
307
308 pool.clear();
310 assert_eq!(pool.poolsize(), 0);
311 }
312
313 #[test]
314 fn test_memory_optimizer() {
315 let mut optimizer = MemoryOptimizer::new();
316
317 optimizer.record_usage(3000);
319 optimizer.record_usage(6000); optimizer.record_usage(4500);
321
322 let efficiency = optimizer.efficiency_score(10000);
323 assert!((0.0..=100.0).contains(&efficiency));
324
325 let suggestions = optimizer.get_suggestions(10000);
326 assert!(!suggestions.is_empty());
328 }
329
330 #[test]
331 fn test_memory_strategy_suggestions() {
332 let strategy = suggest_memory_strategy(1000000, 1000000, true);
334 assert!(matches!(strategy, MemoryStrategy::Unified));
335
336 let strategy = suggest_memory_strategy(10000, 1000000, false);
338 assert!(matches!(strategy, MemoryStrategy::Pinned));
339
340 let strategy = suggest_memory_strategy(300000, 1000000, false);
342 assert!(matches!(strategy, MemoryStrategy::Pooled));
343 }
344
345 #[test]
346 fn test_check_memory_requirements() {
347 let backend = CpuFallbackBackend::new();
348 let context = backend.create_context(0).expect("Operation failed");
349
350 let matrices = vec![(10, 10), (10, 10)];
352 let fits =
353 check_memory_requirements(context.as_ref(), &matrices, 8).expect("Operation failed");
354 assert!(fits);
355
356 let matrices = vec![(100000, 100000)];
358 let fits =
359 check_memory_requirements(context.as_ref(), &matrices, 8).expect("Operation failed");
360 let _ = fits;
362 }
363}