1use crate::{Device, Result, TensorError};
7use std::collections::{HashMap, VecDeque};
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone)]
13pub struct MemoryPoolStats {
14 pub total_allocated: usize,
15 pub total_free: usize,
16 pub blocks_allocated: usize,
17 pub blocks_free: usize,
18 pub fragmentation_ratio: f32,
19 pub peak_allocated: usize,
20 pub allocation_count: u64,
21 pub deallocation_count: u64,
22 pub defragmentation_count: u64,
23 pub largest_free_block: usize,
24 pub average_block_size: f32,
25 pub memory_pressure: f32,
26}
27
28#[derive(Debug, Clone)]
30pub struct AllocationTracker {
31 pub timestamp: Instant,
32 pub size: usize,
33 pub block_idx: usize,
34 pub lifetime_us: Option<u64>,
35 pub deallocated_at: Option<Instant>,
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub enum MemoryPressureLevel {
41 Low, Medium, High, Critical, }
46
47#[derive(Debug, Clone)]
49pub(crate) struct MemoryBlock {
50 #[allow(dead_code)] pub offset: usize,
52 pub size: usize,
53 pub is_free: bool,
54 #[allow(dead_code)] pub ref_count: usize, }
57
58impl MemoryBlock {
59 #[allow(dead_code)] pub fn new_free(offset: usize, size: usize) -> Self {
62 Self {
63 offset,
64 size,
65 is_free: true,
66 ref_count: 0,
67 }
68 }
69
70 #[allow(dead_code)] pub fn new_allocated(offset: usize, size: usize) -> Self {
73 Self {
74 offset,
75 size,
76 is_free: false,
77 ref_count: 1, }
79 }
80
81 #[allow(dead_code)] pub fn add_ref(&mut self) {
84 assert!(!self.is_free, "Cannot add reference to free block");
85 self.ref_count += 1;
86 }
87
88 #[allow(dead_code)] pub fn release_ref(&mut self) -> bool {
91 assert!(!self.is_free, "Cannot release reference from free block");
92 assert!(self.ref_count > 0, "Reference count underflow");
93
94 self.ref_count -= 1;
95 self.ref_count == 0 }
97
98 #[allow(dead_code)] pub fn can_free(&self) -> bool {
101 !self.is_free && self.ref_count == 0
102 }
103}
104
105#[derive(Debug)]
107pub struct MemoryPool {
108 #[allow(dead_code)]
109 device: Device,
110 #[cfg(feature = "gpu")]
111 gpu_device: Arc<wgpu::Device>,
112 #[cfg(feature = "gpu")]
113 gpu_queue: Arc<wgpu::Queue>,
114
115 #[allow(dead_code)]
117 pool_size: usize,
118 #[cfg(feature = "gpu")]
119 pool_buffer: wgpu::Buffer,
120
121 #[allow(dead_code)]
123 blocks: Arc<RwLock<Vec<MemoryBlock>>>,
124 #[allow(dead_code)]
125 free_blocks: Arc<Mutex<VecDeque<usize>>>, stats: Arc<Mutex<MemoryPoolStats>>,
129 #[allow(dead_code)]
130 allocation_history: Arc<Mutex<HashMap<usize, AllocationTracker>>>,
131
132 #[allow(dead_code)]
134 auto_defrag_threshold: f32, #[allow(dead_code)]
136 defrag_last_run: Arc<Mutex<Instant>>,
137 #[allow(dead_code)]
138 defrag_min_interval: Duration, }
140
141impl MemoryPool {
142 #[cfg(feature = "gpu")]
144 pub fn new(device_id: usize, pool_size: usize) -> Result<Self> {
145 let gpu_ctx = crate::device::context::get_gpu_context(device_id)?;
146
147 let pool_buffer = gpu_ctx.device.create_buffer(&wgpu::BufferDescriptor {
149 label: Some("memory_pool_buffer"),
150 size: pool_size as u64,
151 usage: wgpu::BufferUsages::STORAGE
152 | wgpu::BufferUsages::COPY_SRC
153 | wgpu::BufferUsages::COPY_DST,
154 mapped_at_creation: false,
155 });
156
157 let blocks = vec![MemoryBlock::new_free(0, pool_size)];
159
160 let mut free_blocks = VecDeque::new();
161 free_blocks.push_back(0);
162
163 let stats = MemoryPoolStats {
164 total_allocated: 0,
165 total_free: pool_size,
166 blocks_allocated: 0,
167 blocks_free: 1,
168 fragmentation_ratio: 0.0,
169 peak_allocated: 0,
170 allocation_count: 0,
171 deallocation_count: 0,
172 defragmentation_count: 0,
173 largest_free_block: pool_size,
174 average_block_size: pool_size as f32,
175 memory_pressure: 0.0,
176 };
177
178 Ok(Self {
179 device: Device::Gpu(device_id),
180 gpu_device: gpu_ctx.device.clone(),
181 gpu_queue: gpu_ctx.queue.clone(),
182 pool_size,
183 pool_buffer,
184 blocks: Arc::new(RwLock::new(blocks)),
185 free_blocks: Arc::new(Mutex::new(free_blocks)),
186 stats: Arc::new(Mutex::new(stats)),
187 allocation_history: Arc::new(Mutex::new(HashMap::new())),
188 auto_defrag_threshold: 0.25, defrag_last_run: Arc::new(Mutex::new(Instant::now())),
190 defrag_min_interval: Duration::from_secs(30), })
192 }
193
194 #[cfg(feature = "gpu")]
196 pub fn allocate(&self, size: usize, alignment: usize) -> Result<PooledBuffer<'_>> {
197 let aligned_size = align_size(size, alignment);
198
199 let mut free_blocks = self
200 .free_blocks
201 .lock()
202 .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
203 let mut blocks = self.blocks.write().map_err(|_| {
204 TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
205 })?;
206
207 let mut best_block_idx = None;
209 let mut best_size = usize::MAX;
210
211 for &block_idx in free_blocks.iter() {
212 let block = &blocks[block_idx];
213 if block.is_free && block.size >= aligned_size && block.size < best_size {
214 best_block_idx = Some(block_idx);
215 best_size = block.size;
216 }
217 }
218
219 if let Some(block_idx) = best_block_idx {
220 let (offset, block_size) = {
222 let block = &blocks[block_idx];
223 (block.offset, block.size)
224 };
225
226 if block_size > aligned_size {
228 let new_block =
230 MemoryBlock::new_free(offset + aligned_size, block_size - aligned_size);
231 blocks.push(new_block);
232 free_blocks.push_back(blocks.len() - 1);
233 }
234
235 blocks[block_idx] = MemoryBlock::new_allocated(offset, aligned_size);
237
238 free_blocks.retain(|&idx| idx != block_idx);
240
241 let mut history = self.allocation_history.lock().map_err(|_| {
243 TensorError::invalid_operation_simple("pool lock poisoned".to_string())
244 })?;
245 history.insert(
246 block_idx,
247 AllocationTracker {
248 timestamp: Instant::now(),
249 size: aligned_size,
250 block_idx,
251 lifetime_us: None,
252 deallocated_at: None,
253 },
254 );
255
256 self.update_enhanced_stats(&blocks);
258
259 #[cfg(feature = "gpu")]
261 self.maybe_auto_defrag();
262
263 Ok(PooledBuffer {
264 pool: self,
265 block_idx,
266 offset,
267 size: aligned_size,
268 })
269 } else {
270 Err(TensorError::allocation_error_simple(format!(
271 "Cannot allocate {} bytes from memory pool",
272 aligned_size
273 )))
274 }
275 }
276
277 #[cfg(feature = "gpu")]
279 pub(crate) fn deallocate(&self, block_idx: usize) -> Result<()> {
280 let mut blocks = self.blocks.write().map_err(|_| {
281 TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
282 })?;
283 let mut free_blocks = self
284 .free_blocks
285 .lock()
286 .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
287
288 let block = &mut blocks[block_idx];
289 if block.is_free {
290 return Err(TensorError::invalid_argument(
291 "Attempting to deallocate already free block".to_string(),
292 ));
293 }
294
295 if block.release_ref() {
297 block.is_free = true;
299 free_blocks.push_back(block_idx);
300 }
301
302 let mut history = self
304 .allocation_history
305 .lock()
306 .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
307 if let Some(_tracker) = history.remove(&block_idx) {
308 }
310
311 self.coalesce_blocks(&mut blocks, &mut free_blocks);
313
314 self.update_enhanced_stats(&blocks);
316
317 Ok(())
318 }
319
320 #[cfg(feature = "gpu")]
323 pub fn share_buffer(&self, block_idx: usize) -> Result<bool> {
324 let mut blocks = self.blocks.write().map_err(|_| {
325 TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
326 })?;
327
328 if block_idx >= blocks.len() {
329 return Err(TensorError::invalid_argument(format!(
330 "Invalid block index: {}",
331 block_idx
332 )));
333 }
334
335 let block = &mut blocks[block_idx];
336 if block.is_free {
337 return Err(TensorError::invalid_argument(
338 "Cannot share a free block".to_string(),
339 ));
340 }
341
342 block.add_ref();
343 Ok(true)
344 }
345
346 #[cfg(feature = "gpu")]
349 pub fn release_buffer(&self, block_idx: usize) -> Result<bool> {
350 let mut blocks = self.blocks.write().map_err(|_| {
351 TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
352 })?;
353 let mut free_blocks = self
354 .free_blocks
355 .lock()
356 .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
357
358 if block_idx >= blocks.len() {
359 return Err(TensorError::invalid_argument(format!(
360 "Invalid block index: {}",
361 block_idx
362 )));
363 }
364
365 let block = &mut blocks[block_idx];
366 if block.is_free {
367 return Err(TensorError::invalid_argument(
368 "Cannot release reference to already free block".to_string(),
369 ));
370 }
371
372 if block.release_ref() {
373 block.is_free = true;
375 free_blocks.push_back(block_idx);
376
377 let mut history = self.allocation_history.lock().map_err(|_| {
379 TensorError::invalid_operation_simple("pool lock poisoned".to_string())
380 })?;
381 if let Some(_tracker) = history.remove(&block_idx) {
382 }
384
385 self.update_enhanced_stats(&blocks);
387
388 Ok(true) } else {
390 Ok(false) }
392 }
393
394 #[cfg(feature = "gpu")]
396 pub fn get_buffer_ref_count(&self, block_idx: usize) -> Result<usize> {
397 let blocks = self.blocks.read().map_err(|_| {
398 TensorError::invalid_operation_simple("pool read lock poisoned".to_string())
399 })?;
400
401 if block_idx >= blocks.len() {
402 return Err(TensorError::invalid_argument(format!(
403 "Invalid block index: {}",
404 block_idx
405 )));
406 }
407
408 let block = &blocks[block_idx];
409 if block.is_free {
410 Ok(0)
411 } else {
412 Ok(block.ref_count)
413 }
414 }
415
416 #[cfg(feature = "gpu")]
418 fn coalesce_blocks(&self, blocks: &mut [MemoryBlock], free_blocks: &mut VecDeque<usize>) {
419 let mut free_indices: Vec<_> = free_blocks.iter().copied().collect();
421 free_indices.sort_by_key(|&idx| blocks[idx].offset);
422
423 let mut coalesced = Vec::new();
424 let mut i = 0;
425
426 while i < free_indices.len() {
427 let mut current_idx = free_indices[i];
428 let mut current_block = blocks[current_idx].clone();
429
430 while i + 1 < free_indices.len() {
432 let next_idx = free_indices[i + 1];
433 let next_block = &blocks[next_idx];
434
435 if current_block.offset + current_block.size == next_block.offset {
437 current_block.size += next_block.size;
439 i += 1; } else {
441 break;
442 }
443 }
444
445 blocks[current_idx] = current_block;
447 coalesced.push(current_idx);
448 i += 1;
449 }
450
451 free_blocks.clear();
453 for idx in coalesced {
454 free_blocks.push_back(idx);
455 }
456 }
457
458 #[allow(dead_code)]
460 fn update_enhanced_stats(&self, blocks: &[MemoryBlock]) {
461 let mut stats = self
462 .stats
463 .lock()
464 .unwrap_or_else(|poisoned| poisoned.into_inner());
465 stats.blocks_allocated = 0;
466 stats.blocks_free = 0;
467 stats.total_allocated = 0;
468 stats.total_free = 0;
469 stats.largest_free_block = 0;
470
471 let mut block_sizes = Vec::new();
472
473 for block in blocks {
474 block_sizes.push(block.size);
475 if block.is_free {
476 stats.blocks_free += 1;
477 stats.total_free += block.size;
478 stats.largest_free_block = stats.largest_free_block.max(block.size);
479 } else {
480 stats.blocks_allocated += 1;
481 stats.total_allocated += block.size;
482 }
483 }
484
485 stats.peak_allocated = stats.peak_allocated.max(stats.total_allocated);
487
488 stats.allocation_count += 1;
490
491 if stats.total_free > 0 {
493 stats.fragmentation_ratio =
494 stats.blocks_free as f32 / (stats.total_free as f32 / 1024.0);
495 } else {
496 stats.fragmentation_ratio = 0.0;
497 }
498
499 if !block_sizes.is_empty() {
501 stats.average_block_size =
502 block_sizes.iter().sum::<usize>() as f32 / block_sizes.len() as f32;
503 }
504
505 let usage_ratio = stats.total_allocated as f32 / self.pool_size as f32;
507 stats.memory_pressure = usage_ratio;
508 }
509
510 #[cfg(feature = "gpu")]
512 #[allow(dead_code)]
513 fn maybe_auto_defrag(&self) {
514 let stats = self
515 .stats
516 .lock()
517 .unwrap_or_else(|poisoned| poisoned.into_inner());
518 if stats.fragmentation_ratio > self.auto_defrag_threshold {
519 let mut last_run = self
520 .defrag_last_run
521 .lock()
522 .unwrap_or_else(|poisoned| poisoned.into_inner());
523 if last_run.elapsed() >= self.defrag_min_interval {
524 drop(stats); self.defragment();
526 *last_run = Instant::now();
527 }
528 }
529 }
530
531 #[cfg(feature = "gpu")]
533 #[allow(dead_code)]
534 pub fn defragment(&self) {
535 let mut blocks = self
536 .blocks
537 .write()
538 .unwrap_or_else(|poisoned| poisoned.into_inner());
539 let mut free_blocks = self
540 .free_blocks
541 .lock()
542 .unwrap_or_else(|poisoned| poisoned.into_inner());
543
544 blocks.sort_by_key(|block| block.offset);
546
547 free_blocks.clear();
549 for (idx, block) in blocks.iter().enumerate() {
550 if block.is_free {
551 free_blocks.push_back(idx);
552 }
553 }
554
555 self.coalesce_blocks(&mut blocks, &mut free_blocks);
557
558 self.update_enhanced_stats(&blocks);
560 let mut stats = self
561 .stats
562 .lock()
563 .unwrap_or_else(|poisoned| poisoned.into_inner());
564 stats.defragmentation_count += 1;
565 }
566
567 #[allow(dead_code)]
569 pub fn memory_pressure_level(&self) -> MemoryPressureLevel {
570 let stats = self
571 .stats
572 .lock()
573 .unwrap_or_else(|poisoned| poisoned.into_inner());
574 match stats.memory_pressure {
575 p if p < 0.5 => MemoryPressureLevel::Low,
576 p if p < 0.8 => MemoryPressureLevel::Medium,
577 p if p < 0.95 => MemoryPressureLevel::High,
578 _ => MemoryPressureLevel::Critical,
579 }
580 }
581
582 #[cfg(feature = "gpu")]
584 #[allow(dead_code)]
585 pub fn aggressive_cleanup(&self, min_block_size: usize) -> Result<usize> {
586 let mut blocks = self.blocks.write().map_err(|_| {
587 TensorError::invalid_operation_simple("pool write lock poisoned".to_string())
588 })?;
589 let mut free_blocks = self
590 .free_blocks
591 .lock()
592 .map_err(|_| TensorError::invalid_operation_simple("pool lock poisoned".to_string()))?;
593
594 let mut removed_count = 0;
595
596 let mut i = 0;
598 while i < blocks.len() {
599 if blocks[i].is_free && blocks[i].size < min_block_size {
600 blocks.remove(i);
601 removed_count += 1;
602 } else {
603 i += 1;
604 }
605 }
606
607 free_blocks.clear();
609 for (idx, block) in blocks.iter().enumerate() {
610 if block.is_free {
611 free_blocks.push_back(idx);
612 }
613 }
614
615 self.coalesce_blocks(&mut blocks, &mut free_blocks);
617
618 self.update_enhanced_stats(&blocks);
620
621 Ok(removed_count)
622 }
623
624 pub fn stats(&self) -> MemoryPoolStats {
626 self.stats
627 .lock()
628 .unwrap_or_else(|poisoned| poisoned.into_inner())
629 .clone()
630 }
631
632 #[cfg(feature = "gpu")]
634 pub fn buffer(&self) -> &wgpu::Buffer {
635 &self.pool_buffer
636 }
637
638 #[cfg(feature = "gpu")]
640 pub fn device(&self) -> &wgpu::Device {
641 &self.gpu_device
642 }
643
644 #[cfg(feature = "gpu")]
646 pub fn queue(&self) -> &wgpu::Queue {
647 &self.gpu_queue
648 }
649}
650
651#[derive(Debug)]
653pub struct PooledBuffer<'a> {
654 #[allow(dead_code)]
655 pool: &'a MemoryPool,
656 #[allow(dead_code)]
657 block_idx: usize,
658 offset: usize,
659 size: usize,
660}
661
662impl<'a> PooledBuffer<'a> {
663 pub fn offset(&self) -> usize {
665 self.offset
666 }
667
668 pub fn size(&self) -> usize {
670 self.size
671 }
672
673 #[cfg(feature = "gpu")]
675 pub fn buffer(&self) -> &wgpu::Buffer {
676 self.pool.buffer()
677 }
678
679 pub fn view(&'a self, offset: usize, size: usize) -> Result<BufferView<'a>> {
681 if offset + size > self.size {
682 return Err(TensorError::invalid_argument(format!(
683 "View out of bounds: offset={}, size={}, buffer_size={}",
684 offset, size, self.size
685 )));
686 }
687
688 Ok(BufferView {
689 buffer: self,
690 view_offset: offset,
691 view_size: size,
692 })
693 }
694}
695
696#[cfg(feature = "gpu")]
697impl<'a> Drop for PooledBuffer<'a> {
698 fn drop(&mut self) {
699 let _ = self.pool.deallocate(self.block_idx);
701 }
702}
703
704pub struct BufferView<'a> {
706 buffer: &'a PooledBuffer<'a>,
707 view_offset: usize,
708 view_size: usize,
709}
710
711impl<'a> BufferView<'a> {
712 pub fn absolute_offset(&self) -> usize {
714 self.buffer.offset() + self.view_offset
715 }
716
717 pub fn size(&self) -> usize {
719 self.view_size
720 }
721
722 #[cfg(feature = "gpu")]
724 pub fn buffer(&self) -> &wgpu::Buffer {
725 self.buffer.buffer()
726 }
727}
728
729#[allow(dead_code)]
731pub fn align_size(size: usize, alignment: usize) -> usize {
732 (size + alignment - 1) & !(alignment - 1)
733}
734
735#[cfg(test)]
736mod tests {
737 use super::*;
738
739 #[test]
740 fn test_align_size() {
741 assert_eq!(align_size(13, 8), 16);
742 assert_eq!(align_size(16, 8), 16);
743 assert_eq!(align_size(17, 8), 24);
744 }
745
746 #[test]
747 fn test_memory_block() {
748 let block = MemoryBlock::new_free(0, 1024);
749 assert!(block.is_free);
750 assert_eq!(block.size, 1024);
751 assert_eq!(block.ref_count, 0);
752
753 let mut allocated_block = MemoryBlock::new_allocated(1024, 512);
754 assert!(!allocated_block.is_free);
755 assert_eq!(allocated_block.ref_count, 1);
756
757 allocated_block.add_ref();
758 assert_eq!(allocated_block.ref_count, 2);
759
760 assert!(!allocated_block.release_ref());
761 assert_eq!(allocated_block.ref_count, 1);
762
763 assert!(allocated_block.release_ref());
764 assert_eq!(allocated_block.ref_count, 0);
765 }
766
767 #[test]
768 fn test_memory_pressure_level() {
769 let pressure = MemoryPressureLevel::Low;
770 assert_eq!(pressure, MemoryPressureLevel::Low);
771
772 let high_pressure = MemoryPressureLevel::High;
773 assert_eq!(high_pressure, MemoryPressureLevel::High);
774 }
775}