1#![allow(dead_code)]
7
8use std::collections::{HashMap, VecDeque};
9use std::ptr::NonNull;
10use std::sync::{Arc, Mutex};
11
12#[cfg(feature = "scirs")]
13use scirs2_core::gpu;
14
15#[cfg(feature = "scirs")]
17pub struct GpuContext;
18
19#[cfg(feature = "scirs")]
20impl GpuContext {
21 pub fn new(_device_id: u32) -> Result<Self, Box<dyn std::error::Error>> {
22 Ok(Self)
23 }
24}
25
26#[cfg(feature = "scirs")]
27#[derive(Clone, Default)]
28pub struct GpuMemory {
29 id: usize,
30 size: usize,
31}
32
33#[cfg(feature = "scirs")]
35#[derive(Clone)]
36struct MemoryBlock {
37 id: usize,
39 size: usize,
41 in_use: bool,
43 last_access: std::time::Instant,
45}
46
47pub struct GpuMemoryPool {
49 #[cfg(feature = "scirs")]
51 context: Arc<GpuContext>,
52 #[cfg(feature = "scirs")]
54 blocks_by_size: HashMap<usize, VecDeque<MemoryBlock>>,
55 #[cfg(feature = "scirs")]
57 all_blocks: Vec<MemoryBlock>,
58 max_size: usize,
60 current_size: usize,
62 stats: AllocationStats,
64 mutex: Arc<Mutex<()>>,
66 next_block_id: usize,
68}
69
70#[derive(Default, Clone)]
72pub struct AllocationStats {
73 pub total_allocations: usize,
75 pub cache_hits: usize,
77 pub cache_misses: usize,
79 pub total_bytes_allocated: usize,
81 pub peak_memory_usage: usize,
83 pub evictions: usize,
85}
86
87#[cfg(feature = "scirs")]
88impl GpuMemoryPool {
89 pub fn new(context: Arc<GpuContext>, max_size: usize) -> Self {
91 Self {
92 context,
93 blocks_by_size: HashMap::new(),
94 all_blocks: Vec::new(),
95 max_size,
96 current_size: 0,
97 stats: AllocationStats::default(),
98 mutex: Arc::new(Mutex::new(())),
99 next_block_id: 0,
100 }
101 }
102
103 #[cfg(feature = "scirs")]
105 pub fn allocate(&mut self, size: usize) -> Result<GpuMemory, String> {
106 let _lock = self
107 .mutex
108 .lock()
109 .map_err(|e| format!("Failed to acquire lock in allocate: {e}"))?;
110
111 self.stats.total_allocations += 1;
112
113 let aligned_size = size.next_power_of_two();
115
116 if let Some(blocks) = self.blocks_by_size.get_mut(&aligned_size) {
118 if let Some(mut block) = blocks.pop_front() {
119 if !block.in_use {
120 block.in_use = true;
121 block.last_access = std::time::Instant::now();
122 self.stats.cache_hits += 1;
123
124 for b in &mut self.all_blocks {
126 if b.id == block.id {
127 b.in_use = true;
128 b.last_access = block.last_access;
129 break;
130 }
131 }
132
133 return Ok(GpuMemory {
134 id: block.id,
135 size: block.size,
136 });
137 }
138 }
139 }
140
141 self.stats.cache_misses += 1;
143
144 if self.current_size + aligned_size > self.max_size {
146 drop(_lock);
148 self.evict_lru_blocks(aligned_size)?;
149 let _lock = self
151 .mutex
152 .lock()
153 .map_err(|e| format!("Failed to re-acquire lock after eviction: {e}"))?;
154 }
155
156 let block_id = self.next_block_id;
158 self.next_block_id += 1;
159
160 let block = MemoryBlock {
161 id: block_id,
162 size: aligned_size,
163 in_use: true,
164 last_access: std::time::Instant::now(),
165 };
166
167 self.all_blocks.push(block);
168 self.current_size += aligned_size;
169 self.stats.total_bytes_allocated += aligned_size;
170
171 if self.current_size > self.stats.peak_memory_usage {
172 self.stats.peak_memory_usage = self.current_size;
173 }
174
175 Ok(GpuMemory {
176 id: block_id,
177 size: aligned_size,
178 })
179 }
180
181 #[cfg(feature = "scirs")]
183 pub fn release(&mut self, memory: GpuMemory) {
184 if let Ok(_lock) = self.mutex.lock() {
186 for block in &mut self.all_blocks {
188 if block.id == memory.id {
189 block.in_use = false;
190 block.last_access = std::time::Instant::now();
191
192 self.blocks_by_size
194 .entry(block.size)
195 .or_default()
196 .push_back(block.clone());
197
198 break;
199 }
200 }
201 }
202 }
204
205 #[cfg(feature = "scirs")]
207 fn evict_lru_blocks(&mut self, required_size: usize) -> Result<(), String> {
208 let mut freed_size = 0;
209 let mut blocks_to_evict = Vec::new();
210
211 let mut free_blocks: Vec<_> = self.all_blocks.iter().filter(|b| !b.in_use).collect();
213 free_blocks.sort_by_key(|b| b.last_access);
214
215 for block in free_blocks {
217 if freed_size >= required_size {
218 break;
219 }
220
221 blocks_to_evict.push(block.id);
222 freed_size += block.size;
223 self.stats.evictions += 1;
224 }
225
226 if freed_size < required_size {
227 return Err("Insufficient memory in pool even after eviction".to_string());
228 }
229
230 for block_id in blocks_to_evict {
232 self.all_blocks.retain(|b| b.id != block_id);
233
234 for blocks in self.blocks_by_size.values_mut() {
236 blocks.retain(|b| b.id != block_id);
237 }
238
239 }
247
248 self.current_size -= freed_size;
249
250 Ok(())
251 }
252
253 pub fn stats(&self) -> AllocationStats {
255 self.stats.clone()
256 }
257
258 #[cfg(feature = "scirs")]
260 pub fn clear(&mut self) -> Result<(), String> {
261 let _lock = self
262 .mutex
263 .lock()
264 .map_err(|e| format!("Failed to acquire lock in clear: {e}"))?;
265
266 self.blocks_by_size.clear();
270 self.all_blocks.clear();
271 self.current_size = 0;
272
273 Ok(())
274 }
275
276 #[cfg(feature = "scirs")]
278 pub fn defragment(&mut self) -> Result<(), String> {
279 let _lock = self
280 .mutex
281 .lock()
282 .map_err(|e| format!("Failed to acquire lock in defragment: {e}"))?;
283
284 for blocks in self.blocks_by_size.values_mut() {
293 blocks.retain(|b| !b.in_use);
294 }
295
296 Ok(())
297 }
298}
299
300pub struct ScopedGpuMemory {
302 memory: Option<GpuMemory>,
303 pool: Arc<Mutex<GpuMemoryPool>>,
304}
305
306impl ScopedGpuMemory {
307 #[cfg(feature = "scirs")]
309 pub fn new(pool: Arc<Mutex<GpuMemoryPool>>, size: usize) -> Result<Self, String> {
310 let memory = pool
311 .lock()
312 .map_err(|e| format!("Failed to acquire pool lock: {e}"))?
313 .allocate(size)?;
314 Ok(Self {
315 memory: Some(memory),
316 pool,
317 })
318 }
319
320 #[cfg(feature = "scirs")]
325 pub fn memory(&self) -> &GpuMemory {
326 self.memory
327 .as_ref()
328 .expect("ScopedGpuMemory::memory called after memory was released - this is a bug")
329 }
330
331 #[cfg(feature = "scirs")]
336 pub fn memory_mut(&mut self) -> &mut GpuMemory {
337 self.memory
338 .as_mut()
339 .expect("ScopedGpuMemory::memory_mut called after memory was released - this is a bug")
340 }
341}
342
343#[cfg(feature = "scirs")]
344impl Drop for ScopedGpuMemory {
345 fn drop(&mut self) {
346 if let Some(memory) = self.memory.take() {
347 if let Ok(mut pool) = self.pool.lock() {
349 pool.release(memory);
350 }
351 }
353 }
354}
355
356pub struct MultiDeviceMemoryPool {
358 device_pools: HashMap<usize, Arc<Mutex<GpuMemoryPool>>>,
360}
361
362impl Default for MultiDeviceMemoryPool {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368impl MultiDeviceMemoryPool {
369 pub fn new() -> Self {
371 Self {
372 device_pools: HashMap::new(),
373 }
374 }
375
376 #[cfg(feature = "scirs")]
378 pub fn add_device(&mut self, device_id: usize, context: Arc<GpuContext>, max_size: usize) {
379 let pool = Arc::new(Mutex::new(GpuMemoryPool::new(context, max_size)));
380 self.device_pools.insert(device_id, pool);
381 }
382
383 pub fn get_pool(&self, device_id: usize) -> Option<Arc<Mutex<GpuMemoryPool>>> {
385 self.device_pools.get(&device_id).cloned()
386 }
387
388 #[cfg(feature = "scirs")]
390 pub fn allocate(&self, device_id: usize, size: usize) -> Result<ScopedGpuMemory, String> {
391 let pool = self
392 .get_pool(device_id)
393 .ok_or_else(|| format!("No pool for device {device_id}"))?;
394
395 ScopedGpuMemory::new(pool, size)
396 }
397
398 pub fn combined_stats(&self) -> AllocationStats {
402 let mut combined = AllocationStats::default();
403
404 for pool in self.device_pools.values() {
405 if let Ok(pool_guard) = pool.lock() {
407 let stats = pool_guard.stats();
408 combined.total_allocations += stats.total_allocations;
409 combined.cache_hits += stats.cache_hits;
410 combined.cache_misses += stats.cache_misses;
411 combined.total_bytes_allocated += stats.total_bytes_allocated;
412 combined.peak_memory_usage += stats.peak_memory_usage;
413 combined.evictions += stats.evictions;
414 }
415 }
417
418 combined
419 }
420}
421
422#[cfg(not(feature = "scirs"))]
424pub struct GpuMemory;
425
426#[cfg(not(feature = "scirs"))]
427impl GpuMemoryPool {
428 pub fn new(_max_size: usize) -> Self {
429 Self {
430 max_size: 0,
431 current_size: 0,
432 stats: AllocationStats::default(),
433 mutex: Arc::new(Mutex::new(())),
434 next_block_id: 0,
435 }
436 }
437
438 pub fn allocate(&mut self, _size: usize) -> Result<GpuMemory, String> {
439 Err("GPU memory pooling requires SciRS2 feature".to_string())
440 }
441
442 pub fn stats(&self) -> AllocationStats {
443 self.stats.clone()
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_allocation_stats() {
453 let stats = AllocationStats {
454 total_allocations: 100,
455 cache_hits: 80,
456 cache_misses: 20,
457 total_bytes_allocated: 1024 * 1024,
458 peak_memory_usage: 512 * 1024,
459 evictions: 5,
460 };
461
462 assert_eq!(stats.total_allocations, 100);
463 assert_eq!(stats.cache_hits, 80);
464
465 let hit_rate = stats.cache_hits as f64 / stats.total_allocations as f64;
466 assert!(hit_rate > 0.79 && hit_rate < 0.81);
467 }
468
469 #[test]
470 #[cfg(feature = "scirs")]
471 fn test_memory_pool_basic() {
472 use crate::gpu_memory_pool::GpuContext;
473
474 let context = Arc::new(GpuContext::new(0).expect("Failed to create GPU context for test"));
475 let mut pool = GpuMemoryPool::new(context, 1024 * 1024); let mem1 = pool
479 .allocate(1024)
480 .expect("First allocation should succeed");
481 assert_eq!(pool.stats().cache_misses, 1);
482 assert_eq!(pool.stats().cache_hits, 0);
483
484 pool.release(mem1);
486 let _mem2 = pool
487 .allocate(1024)
488 .expect("Second allocation should succeed");
489 assert_eq!(pool.stats().cache_misses, 1);
490 assert_eq!(pool.stats().cache_hits, 1);
491 }
492}