1use std::collections::HashMap;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
53pub enum SizeClass {
54 Small,
56 Medium,
58 Large,
60 Huge,
62}
63
64impl SizeClass {
65 pub fn classify(bytes: u64) -> SizeClass {
67 if bytes <= 4_096 {
68 SizeClass::Small
69 } else if bytes <= 65_536 {
70 SizeClass::Medium
71 } else if bytes <= 1_048_576 {
72 SizeClass::Large
73 } else {
74 SizeClass::Huge
75 }
76 }
77
78 pub fn bucket_size(&self) -> u64 {
80 match self {
81 SizeClass::Small => 4_096,
82 SizeClass::Medium => 65_536,
83 SizeClass::Large => 1_048_576,
84 SizeClass::Huge => 16_777_216,
85 }
86 }
87}
88
89#[derive(Clone, Debug)]
95pub struct PoolSlot {
96 pub slot_id: u64,
98 pub size_class: SizeClass,
100 pub allocated_bytes: u64,
102 pub in_use: bool,
104 pub last_used_tick: u64,
106}
107
108#[derive(Clone, Debug)]
114pub struct MemoryPoolStats {
115 pub total_slots: usize,
117 pub in_use_slots: usize,
119 pub free_slots: usize,
121 pub total_allocated_bytes: u64,
123 pub wasted_bytes: u64,
125}
126
127impl MemoryPoolStats {
128 pub fn utilization(&self) -> f64 {
130 if self.total_slots == 0 {
131 0.0
132 } else {
133 self.in_use_slots as f64 / self.total_slots as f64
134 }
135 }
136}
137
138pub struct TensorMemoryPool {
145 pub slots: HashMap<u64, PoolSlot>,
147 pub next_slot_id: u64,
149 pub max_slots: usize,
151 pub total_allocations: u64,
153 pub total_deallocations: u64,
155}
156
157impl TensorMemoryPool {
158 pub fn new(max_slots: usize) -> Self {
160 TensorMemoryPool {
161 slots: HashMap::new(),
162 next_slot_id: 0,
163 max_slots,
164 total_allocations: 0,
165 total_deallocations: 0,
166 }
167 }
168
169 pub fn allocate(&mut self, requested_bytes: u64, tick: u64) -> Option<u64> {
172 let class = SizeClass::classify(requested_bytes);
173
174 let reuse_id = self
175 .slots
176 .values()
177 .filter(|s| !s.in_use && s.size_class == class)
178 .map(|s| s.slot_id)
179 .next();
180
181 if let Some(id) = reuse_id {
182 let slot = self.slots.get_mut(&id)?;
183 slot.in_use = true;
184 slot.allocated_bytes = requested_bytes;
185 slot.last_used_tick = tick;
186 self.total_allocations += 1;
187 return Some(id);
188 }
189
190 if self.slots.len() >= self.max_slots {
191 return None;
192 }
193
194 let id = self.next_slot_id;
195 self.next_slot_id += 1;
196
197 let slot = PoolSlot {
198 slot_id: id,
199 size_class: class,
200 allocated_bytes: requested_bytes,
201 in_use: true,
202 last_used_tick: tick,
203 };
204 self.slots.insert(id, slot);
205 self.total_allocations += 1;
206 Some(id)
207 }
208
209 pub fn deallocate(&mut self, slot_id: u64, tick: u64) -> bool {
213 match self.slots.get_mut(&slot_id) {
214 Some(slot) if slot.in_use => {
215 slot.in_use = false;
216 slot.last_used_tick = tick;
217 self.total_deallocations += 1;
218 true
219 }
220 _ => false,
221 }
222 }
223
224 pub fn evict_idle(&mut self, tick: u64, idle_ticks: u64) {
227 self.slots.retain(|_, slot| {
228 if slot.in_use {
229 return true;
230 }
231 let age = tick.saturating_sub(slot.last_used_tick);
232 age < idle_ticks
233 });
234 }
235
236 pub fn slots_for_class(&self, class: SizeClass) -> Vec<&PoolSlot> {
238 let mut result: Vec<&PoolSlot> = self
239 .slots
240 .values()
241 .filter(|s| s.size_class == class)
242 .collect();
243 result.sort_by_key(|s| s.slot_id);
244 result
245 }
246
247 pub fn stats(&self) -> MemoryPoolStats {
249 let total_slots = self.slots.len();
250 let in_use_slots = self.slots.values().filter(|s| s.in_use).count();
251 let free_slots = total_slots - in_use_slots;
252
253 let total_allocated_bytes: u64 = self
254 .slots
255 .values()
256 .map(|s| s.size_class.bucket_size())
257 .sum();
258
259 let wasted_bytes: u64 = self
260 .slots
261 .values()
262 .filter(|s| s.in_use)
263 .map(|s| s.size_class.bucket_size().saturating_sub(s.allocated_bytes))
264 .sum();
265
266 MemoryPoolStats {
267 total_slots,
268 in_use_slots,
269 free_slots,
270 total_allocated_bytes,
271 wasted_bytes,
272 }
273 }
274}
275
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
286pub enum BlockStatus {
287 Free,
289 Allocated,
291 Reserved,
293}
294
295#[derive(Debug, Clone)]
301pub struct MemoryBlock {
302 pub id: u64,
304 pub size: usize,
306 pub status: BlockStatus,
308 pub owner: Option<String>,
310 pub generation: u64,
312}
313
314#[derive(Debug, Clone)]
320pub struct PoolConfig {
321 pub initial_blocks: usize,
323 pub block_size: usize,
325 pub max_blocks: usize,
327 pub allow_growth: bool,
329}
330
331impl Default for PoolConfig {
332 fn default() -> Self {
333 PoolConfig {
334 initial_blocks: 64,
335 block_size: 4096,
336 max_blocks: 1024,
337 allow_growth: true,
338 }
339 }
340}
341
342#[derive(Debug, Clone)]
348pub struct BlockPoolStats {
349 pub total_blocks: usize,
351 pub free_blocks: usize,
353 pub allocated_blocks: usize,
355 pub reserved_blocks: usize,
357 pub peak_allocated: usize,
359 pub total_allocations: u64,
361 pub total_deallocations: u64,
363 pub utilization: f64,
365}
366
367pub struct TensorBlockPool {
377 config: PoolConfig,
378 blocks: Vec<MemoryBlock>,
379 next_id: u64,
380 allocations: u64,
381 deallocations: u64,
382 peak_allocated: usize,
383 current_allocated: usize,
384}
385
386impl TensorBlockPool {
387 pub fn new(config: PoolConfig) -> Self {
389 let mut blocks = Vec::with_capacity(config.initial_blocks);
390 for i in 0..config.initial_blocks {
391 blocks.push(MemoryBlock {
392 id: i as u64,
393 size: config.block_size,
394 status: BlockStatus::Free,
395 owner: None,
396 generation: 0,
397 });
398 }
399 TensorBlockPool {
400 next_id: config.initial_blocks as u64,
401 config,
402 blocks,
403 allocations: 0,
404 deallocations: 0,
405 peak_allocated: 0,
406 current_allocated: 0,
407 }
408 }
409
410 pub fn allocate(&mut self, owner: &str) -> Result<u64, String> {
416 if let Some(block) = self
418 .blocks
419 .iter_mut()
420 .find(|b| b.status == BlockStatus::Free)
421 {
422 block.status = BlockStatus::Allocated;
423 block.owner = Some(owner.to_string());
424 self.allocations += 1;
425 self.current_allocated += 1;
426 if self.current_allocated > self.peak_allocated {
427 self.peak_allocated = self.current_allocated;
428 }
429 return Ok(block.id);
430 }
431
432 if !self.config.allow_growth {
434 return Err("pool exhausted and growth is disabled".to_string());
435 }
436 if self.blocks.len() >= self.config.max_blocks {
437 return Err(format!(
438 "pool exhausted: max_blocks ({}) reached",
439 self.config.max_blocks
440 ));
441 }
442
443 let id = self.next_id;
444 self.next_id += 1;
445 self.blocks.push(MemoryBlock {
446 id,
447 size: self.config.block_size,
448 status: BlockStatus::Allocated,
449 owner: Some(owner.to_string()),
450 generation: 0,
451 });
452 self.allocations += 1;
453 self.current_allocated += 1;
454 if self.current_allocated > self.peak_allocated {
455 self.peak_allocated = self.current_allocated;
456 }
457 Ok(id)
458 }
459
460 pub fn deallocate(&mut self, block_id: u64) -> Result<(), String> {
464 let block = self
465 .blocks
466 .iter_mut()
467 .find(|b| b.id == block_id)
468 .ok_or_else(|| format!("block {} not found", block_id))?;
469
470 if block.status != BlockStatus::Allocated {
471 return Err(format!(
472 "block {} is not allocated (status: {:?})",
473 block_id, block.status
474 ));
475 }
476
477 block.status = BlockStatus::Free;
478 block.owner = None;
479 block.generation += 1;
480 self.deallocations += 1;
481 self.current_allocated = self.current_allocated.saturating_sub(1);
482 Ok(())
483 }
484
485 pub fn reserve(&mut self, block_id: u64, owner: &str) -> Result<(), String> {
490 let block = self
491 .blocks
492 .iter_mut()
493 .find(|b| b.id == block_id)
494 .ok_or_else(|| format!("block {} not found", block_id))?;
495
496 if block.status != BlockStatus::Free {
497 return Err(format!(
498 "block {} is not free (status: {:?})",
499 block_id, block.status
500 ));
501 }
502
503 block.status = BlockStatus::Reserved;
504 block.owner = Some(owner.to_string());
505 Ok(())
506 }
507
508 pub fn release_reservation(&mut self, block_id: u64) -> Result<(), String> {
510 let block = self
511 .blocks
512 .iter_mut()
513 .find(|b| b.id == block_id)
514 .ok_or_else(|| format!("block {} not found", block_id))?;
515
516 if block.status != BlockStatus::Reserved {
517 return Err(format!(
518 "block {} is not reserved (status: {:?})",
519 block_id, block.status
520 ));
521 }
522
523 block.status = BlockStatus::Free;
524 block.owner = None;
525 Ok(())
526 }
527
528 pub fn get_block(&self, block_id: u64) -> Option<&MemoryBlock> {
530 self.blocks.iter().find(|b| b.id == block_id)
531 }
532
533 pub fn free_count(&self) -> usize {
535 self.blocks
536 .iter()
537 .filter(|b| b.status == BlockStatus::Free)
538 .count()
539 }
540
541 pub fn allocated_count(&self) -> usize {
543 self.blocks
544 .iter()
545 .filter(|b| b.status == BlockStatus::Allocated)
546 .count()
547 }
548
549 pub fn utilization(&self) -> f64 {
552 if self.blocks.is_empty() {
553 0.0
554 } else {
555 self.allocated_count() as f64 / self.blocks.len() as f64
556 }
557 }
558
559 pub fn defragment(&mut self) -> usize {
564 let before_free_positions: Vec<usize> = self
567 .blocks
568 .iter()
569 .enumerate()
570 .filter(|(_, b)| b.status == BlockStatus::Free)
571 .map(|(i, _)| i)
572 .collect();
573
574 self.blocks.sort_by_key(|b| {
576 if b.status == BlockStatus::Free {
577 1u8
578 } else {
579 0u8
580 }
581 });
582
583 let mut relocated = 0usize;
585 for block in &mut self.blocks {
586 if block.status == BlockStatus::Free {
587 block.generation = 0;
588 relocated += 1;
589 }
590 }
591
592 let _ = before_free_positions; relocated
597 }
598
599 pub fn shrink_to_fit(&mut self) -> usize {
602 let mut removed = 0usize;
603 while self
604 .blocks
605 .last()
606 .is_some_and(|b| b.status == BlockStatus::Free)
607 {
608 self.blocks.pop();
609 removed += 1;
610 }
611 removed
612 }
613
614 pub fn block_stats(&self) -> BlockPoolStats {
616 let total_blocks = self.blocks.len();
617 let free_blocks = self.free_count();
618 let allocated_blocks = self.allocated_count();
619 let reserved_blocks = self
620 .blocks
621 .iter()
622 .filter(|b| b.status == BlockStatus::Reserved)
623 .count();
624
625 BlockPoolStats {
626 total_blocks,
627 free_blocks,
628 allocated_blocks,
629 reserved_blocks,
630 peak_allocated: self.peak_allocated,
631 total_allocations: self.allocations,
632 total_deallocations: self.deallocations,
633 utilization: self.utilization(),
634 }
635 }
636}
637
638#[cfg(test)]
643mod tests {
644 use super::*;
645
646 #[test]
651 fn slab_new_starts_empty() {
652 let pool = TensorMemoryPool::new(1024);
653 assert_eq!(pool.slots.len(), 0);
654 assert_eq!(pool.total_allocations, 0);
655 assert_eq!(pool.total_deallocations, 0);
656 }
657
658 #[test]
659 fn slab_allocate_creates_slot_when_empty() {
660 let mut pool = TensorMemoryPool::new(1024);
661 let id = pool.allocate(100, 1);
662 assert!(id.is_some());
663 assert_eq!(pool.slots.len(), 1);
664 }
665
666 #[test]
667 fn slab_allocate_reuses_free_slot_of_same_class() {
668 let mut pool = TensorMemoryPool::new(1024);
669 let id1 = pool.allocate(100, 1);
670 if let Some(id) = id1 {
671 pool.deallocate(id, 2);
672 let id2 = pool.allocate(200, 3);
673 assert_eq!(id2, Some(id));
674 assert_eq!(pool.slots.len(), 1);
675 }
676 }
677
678 #[test]
679 fn slab_allocate_creates_new_slot_when_no_free_of_class() {
680 let mut pool = TensorMemoryPool::new(1024);
681 let _id1 = pool.allocate(100, 1);
682 let id2 = pool.allocate(10_000, 2);
683 assert!(id2.is_some());
684 assert_eq!(pool.slots.len(), 2);
685 }
686
687 #[test]
688 fn slab_allocate_returns_none_at_max_slots() {
689 let mut pool = TensorMemoryPool::new(2);
690 let _a = pool.allocate(100, 1);
691 let _b = pool.allocate(200, 2);
692 let c = pool.allocate(300, 3);
693 assert!(c.is_none());
694 }
695
696 #[test]
697 fn slab_allocate_increments_total_allocations() {
698 let mut pool = TensorMemoryPool::new(1024);
699 pool.allocate(100, 1);
700 pool.allocate(200, 2);
701 assert_eq!(pool.total_allocations, 2);
702 }
703
704 #[test]
705 fn classify_boundary_4096() {
706 assert_eq!(SizeClass::classify(4096), SizeClass::Small);
707 }
708
709 #[test]
710 fn classify_boundary_4097() {
711 assert_eq!(SizeClass::classify(4097), SizeClass::Medium);
712 }
713
714 #[test]
715 fn classify_boundary_65536() {
716 assert_eq!(SizeClass::classify(65_536), SizeClass::Medium);
717 }
718
719 #[test]
720 fn classify_boundary_65537() {
721 assert_eq!(SizeClass::classify(65_537), SizeClass::Large);
722 }
723
724 #[test]
725 fn classify_boundary_1048576() {
726 assert_eq!(SizeClass::classify(1_048_576), SizeClass::Large);
727 }
728
729 #[test]
730 fn classify_boundary_1048577() {
731 assert_eq!(SizeClass::classify(1_048_577), SizeClass::Huge);
732 }
733
734 #[test]
735 fn bucket_size_small() {
736 assert_eq!(SizeClass::Small.bucket_size(), 4_096);
737 }
738
739 #[test]
740 fn bucket_size_medium() {
741 assert_eq!(SizeClass::Medium.bucket_size(), 65_536);
742 }
743
744 #[test]
745 fn bucket_size_large() {
746 assert_eq!(SizeClass::Large.bucket_size(), 1_048_576);
747 }
748
749 #[test]
750 fn bucket_size_huge() {
751 assert_eq!(SizeClass::Huge.bucket_size(), 16_777_216);
752 }
753
754 #[test]
755 fn size_class_ordering() {
756 assert!(SizeClass::Small < SizeClass::Medium);
757 assert!(SizeClass::Medium < SizeClass::Large);
758 assert!(SizeClass::Large < SizeClass::Huge);
759 }
760
761 #[test]
762 fn slab_deallocate_marks_slot_free() {
763 let mut pool = TensorMemoryPool::new(1024);
764 if let Some(id) = pool.allocate(100, 1) {
765 assert!(pool.slots[&id].in_use);
766 pool.deallocate(id, 2);
767 assert!(!pool.slots[&id].in_use);
768 }
769 }
770
771 #[test]
772 fn slab_deallocate_returns_false_for_unknown_id() {
773 let mut pool = TensorMemoryPool::new(1024);
774 assert!(!pool.deallocate(9999, 1));
775 }
776
777 #[test]
778 fn slab_deallocate_returns_false_if_already_free() {
779 let mut pool = TensorMemoryPool::new(1024);
780 if let Some(id) = pool.allocate(100, 1) {
781 pool.deallocate(id, 2);
782 assert!(!pool.deallocate(id, 3));
783 }
784 }
785
786 #[test]
787 fn slab_deallocate_increments_total_deallocations() {
788 let mut pool = TensorMemoryPool::new(1024);
789 if let Some(id) = pool.allocate(100, 1) {
790 pool.deallocate(id, 2);
791 assert_eq!(pool.total_deallocations, 1);
792 }
793 }
794
795 #[test]
796 fn slab_evict_idle_removes_old_free_slots() {
797 let mut pool = TensorMemoryPool::new(1024);
798 if let Some(id) = pool.allocate(100, 0) {
799 pool.deallocate(id, 1);
800 pool.evict_idle(100, 10);
801 assert!(pool.slots.is_empty());
802 }
803 }
804
805 #[test]
806 fn slab_evict_idle_keeps_recent_free_slots() {
807 let mut pool = TensorMemoryPool::new(1024);
808 if let Some(id) = pool.allocate(100, 0) {
809 pool.deallocate(id, 95);
810 pool.evict_idle(100, 10);
811 assert_eq!(pool.slots.len(), 1);
812 }
813 }
814
815 #[test]
816 fn slab_evict_idle_does_not_evict_in_use_slots() {
817 let mut pool = TensorMemoryPool::new(1024);
818 let _id = pool.allocate(100, 0);
819 pool.evict_idle(9999, 1);
820 assert_eq!(pool.slots.len(), 1);
821 }
822
823 #[test]
824 fn slab_slots_for_class_filters_and_sorts() {
825 let mut pool = TensorMemoryPool::new(1024);
826 let _s1 = pool.allocate(100, 1);
827 let _m1 = pool.allocate(10_000, 2);
828 let _s2 = pool.allocate(200, 3);
829 let small_slots = pool.slots_for_class(SizeClass::Small);
830 assert_eq!(small_slots.len(), 2);
831 assert!(small_slots[0].slot_id < small_slots[1].slot_id);
832 let medium_slots = pool.slots_for_class(SizeClass::Medium);
833 assert_eq!(medium_slots.len(), 1);
834 }
835
836 #[test]
837 fn slab_stats_total_slots_in_use_free() {
838 let mut pool = TensorMemoryPool::new(1024);
839 if let Some(id1) = pool.allocate(100, 1) {
840 let _id2 = pool.allocate(200, 2);
841 pool.deallocate(id1, 3);
842 let s = pool.stats();
843 assert_eq!(s.total_slots, 2);
844 assert_eq!(s.in_use_slots, 1);
845 assert_eq!(s.free_slots, 1);
846 }
847 }
848
849 #[test]
850 fn slab_stats_total_allocated_bytes() {
851 let mut pool = TensorMemoryPool::new(1024);
852 let _id = pool.allocate(100, 1);
853 let s = pool.stats();
854 assert_eq!(s.total_allocated_bytes, 4_096);
855 }
856
857 #[test]
858 fn slab_stats_wasted_bytes_calculation() {
859 let mut pool = TensorMemoryPool::new(1024);
860 let _id = pool.allocate(100, 1);
861 let s = pool.stats();
862 assert_eq!(s.wasted_bytes, 4_096 - 100);
863 }
864
865 #[test]
866 fn slab_stats_utilization_computed() {
867 let mut pool = TensorMemoryPool::new(1024);
868 if let Some(id1) = pool.allocate(100, 1) {
869 let _id2 = pool.allocate(200, 2);
870 pool.deallocate(id1, 3);
871 let s = pool.stats();
872 let util = s.utilization();
873 assert!((util - 0.5).abs() < f64::EPSILON);
874 }
875 }
876
877 #[test]
878 fn slab_stats_utilization_empty_pool() {
879 let pool = TensorMemoryPool::new(1024);
880 assert_eq!(pool.stats().utilization(), 0.0);
881 }
882
883 #[test]
890 fn block_pool_initial_preallocation() {
891 let config = PoolConfig {
892 initial_blocks: 16,
893 block_size: 1024,
894 max_blocks: 64,
895 allow_growth: true,
896 };
897 let pool = TensorBlockPool::new(config);
898 assert_eq!(pool.blocks.len(), 16);
899 assert_eq!(pool.free_count(), 16);
900 assert_eq!(pool.allocated_count(), 0);
901 }
902
903 #[test]
904 fn block_pool_default_config() {
905 let config = PoolConfig::default();
906 assert_eq!(config.initial_blocks, 64);
907 assert_eq!(config.block_size, 4096);
908 assert_eq!(config.max_blocks, 1024);
909 assert!(config.allow_growth);
910 }
911
912 #[test]
913 fn block_pool_zero_initial_blocks() {
914 let config = PoolConfig {
915 initial_blocks: 0,
916 block_size: 256,
917 max_blocks: 10,
918 allow_growth: true,
919 };
920 let pool = TensorBlockPool::new(config);
921 assert_eq!(pool.blocks.len(), 0);
922 assert_eq!(pool.free_count(), 0);
923 }
924
925 #[test]
928 fn block_pool_allocate_returns_valid_id() {
929 let mut pool = TensorBlockPool::new(PoolConfig::default());
930 let id = pool.allocate("matmul");
931 assert!(id.is_ok());
932 }
933
934 #[test]
935 fn block_pool_allocate_marks_block_allocated() {
936 let mut pool = TensorBlockPool::new(PoolConfig::default());
937 let id = pool.allocate("conv2d").expect("should allocate");
938 let block = pool.get_block(id).expect("block should exist");
939 assert_eq!(block.status, BlockStatus::Allocated);
940 assert_eq!(block.owner.as_deref(), Some("conv2d"));
941 }
942
943 #[test]
944 fn block_pool_deallocate_frees_block() {
945 let mut pool = TensorBlockPool::new(PoolConfig::default());
946 let id = pool.allocate("relu").expect("should allocate");
947 pool.deallocate(id).expect("should deallocate");
948 let block = pool.get_block(id).expect("block should exist");
949 assert_eq!(block.status, BlockStatus::Free);
950 assert!(block.owner.is_none());
951 }
952
953 #[test]
954 fn block_pool_deallocate_nonexistent_errors() {
955 let mut pool = TensorBlockPool::new(PoolConfig::default());
956 let result = pool.deallocate(99999);
957 assert!(result.is_err());
958 }
959
960 #[test]
961 fn block_pool_deallocate_free_block_errors() {
962 let config = PoolConfig {
963 initial_blocks: 4,
964 block_size: 256,
965 max_blocks: 8,
966 allow_growth: false,
967 };
968 let mut pool = TensorBlockPool::new(config);
969 let result = pool.deallocate(0);
971 assert!(result.is_err());
972 }
973
974 #[test]
975 fn block_pool_allocate_deallocate_cycle() {
976 let config = PoolConfig {
977 initial_blocks: 2,
978 block_size: 512,
979 max_blocks: 2,
980 allow_growth: false,
981 };
982 let mut pool = TensorBlockPool::new(config);
983
984 let id1 = pool.allocate("op_a").expect("first alloc");
985 let id2 = pool.allocate("op_b").expect("second alloc");
986 assert!(pool.allocate("op_c").is_err()); pool.deallocate(id1).expect("dealloc id1");
989 let id3 = pool.allocate("op_c").expect("reuse freed block");
990 assert_eq!(id3, id1); pool.deallocate(id2).expect("dealloc id2");
992 pool.deallocate(id3).expect("dealloc id3");
993
994 assert_eq!(pool.free_count(), 2);
995 assert_eq!(pool.allocated_count(), 0);
996 }
997
998 #[test]
1001 fn block_pool_generation_increments_on_reuse() {
1002 let mut pool = TensorBlockPool::new(PoolConfig::default());
1003 let id = pool.allocate("gen_test").expect("alloc");
1004 assert_eq!(pool.get_block(id).expect("exists").generation, 0);
1005
1006 pool.deallocate(id).expect("dealloc");
1007 assert_eq!(pool.get_block(id).expect("exists").generation, 1);
1008
1009 let id2 = pool.allocate("gen_test_2").expect("realloc");
1011 assert_eq!(id2, id);
1012 pool.deallocate(id2).expect("dealloc again");
1013 assert_eq!(pool.get_block(id).expect("exists").generation, 2);
1014 }
1015
1016 #[test]
1019 fn block_pool_reserve_free_block() {
1020 let config = PoolConfig {
1021 initial_blocks: 4,
1022 block_size: 256,
1023 max_blocks: 4,
1024 allow_growth: false,
1025 };
1026 let mut pool = TensorBlockPool::new(config);
1027
1028 pool.reserve(0, "future_op").expect("should reserve");
1029 let block = pool.get_block(0).expect("exists");
1030 assert_eq!(block.status, BlockStatus::Reserved);
1031 assert_eq!(block.owner.as_deref(), Some("future_op"));
1032 }
1033
1034 #[test]
1035 fn block_pool_reserve_non_free_errors() {
1036 let mut pool = TensorBlockPool::new(PoolConfig::default());
1037 let id = pool.allocate("busy").expect("alloc");
1038 let result = pool.reserve(id, "should_fail");
1039 assert!(result.is_err());
1040 }
1041
1042 #[test]
1043 fn block_pool_release_reservation() {
1044 let config = PoolConfig {
1045 initial_blocks: 4,
1046 block_size: 256,
1047 max_blocks: 4,
1048 allow_growth: false,
1049 };
1050 let mut pool = TensorBlockPool::new(config);
1051
1052 pool.reserve(1, "temp").expect("reserve");
1053 pool.release_reservation(1).expect("release");
1054 let block = pool.get_block(1).expect("exists");
1055 assert_eq!(block.status, BlockStatus::Free);
1056 assert!(block.owner.is_none());
1057 }
1058
1059 #[test]
1060 fn block_pool_release_non_reserved_errors() {
1061 let mut pool = TensorBlockPool::new(PoolConfig::default());
1062 let id = pool.allocate("op").expect("alloc");
1063 let result = pool.release_reservation(id);
1064 assert!(result.is_err());
1065 }
1066
1067 #[test]
1068 fn block_pool_reserved_blocks_not_allocatable() {
1069 let config = PoolConfig {
1070 initial_blocks: 1,
1071 block_size: 256,
1072 max_blocks: 1,
1073 allow_growth: false,
1074 };
1075 let mut pool = TensorBlockPool::new(config);
1076 pool.reserve(0, "held").expect("reserve");
1077 let result = pool.allocate("want_block");
1079 assert!(result.is_err());
1080 }
1081
1082 #[test]
1085 fn block_pool_growth_when_allowed() {
1086 let config = PoolConfig {
1087 initial_blocks: 2,
1088 block_size: 128,
1089 max_blocks: 4,
1090 allow_growth: true,
1091 };
1092 let mut pool = TensorBlockPool::new(config);
1093
1094 let _a = pool.allocate("a").expect("alloc a");
1095 let _b = pool.allocate("b").expect("alloc b");
1096 let c = pool.allocate("c");
1098 assert!(c.is_ok());
1099 assert_eq!(pool.blocks.len(), 3);
1100 }
1101
1102 #[test]
1103 fn block_pool_growth_stops_at_max_blocks() {
1104 let config = PoolConfig {
1105 initial_blocks: 1,
1106 block_size: 128,
1107 max_blocks: 2,
1108 allow_growth: true,
1109 };
1110 let mut pool = TensorBlockPool::new(config);
1111
1112 let _a = pool.allocate("a").expect("alloc a");
1113 let _b = pool.allocate("b").expect("alloc b (growth)");
1114 let c = pool.allocate("c");
1115 assert!(c.is_err());
1116 }
1117
1118 #[test]
1119 fn block_pool_no_growth_when_disabled() {
1120 let config = PoolConfig {
1121 initial_blocks: 1,
1122 block_size: 128,
1123 max_blocks: 100,
1124 allow_growth: false,
1125 };
1126 let mut pool = TensorBlockPool::new(config);
1127
1128 let _a = pool.allocate("a").expect("alloc a");
1129 let b = pool.allocate("b");
1130 assert!(b.is_err());
1131 }
1132
1133 #[test]
1136 fn block_pool_utilization_calculation() {
1137 let config = PoolConfig {
1138 initial_blocks: 4,
1139 block_size: 256,
1140 max_blocks: 4,
1141 allow_growth: false,
1142 };
1143 let mut pool = TensorBlockPool::new(config);
1144
1145 let _a = pool.allocate("a").expect("alloc");
1146 let _b = pool.allocate("b").expect("alloc");
1147 let u = pool.utilization();
1149 assert!((u - 0.5).abs() < f64::EPSILON);
1150 }
1151
1152 #[test]
1153 fn block_pool_utilization_empty() {
1154 let config = PoolConfig {
1155 initial_blocks: 0,
1156 block_size: 256,
1157 max_blocks: 10,
1158 allow_growth: true,
1159 };
1160 let pool = TensorBlockPool::new(config);
1161 assert!((pool.utilization()).abs() < f64::EPSILON);
1162 }
1163
1164 #[test]
1167 fn block_pool_peak_allocated_tracking() {
1168 let mut pool = TensorBlockPool::new(PoolConfig::default());
1169
1170 let a = pool.allocate("a").expect("alloc");
1171 let b = pool.allocate("b").expect("alloc");
1172 let _c = pool.allocate("c").expect("alloc");
1173 pool.deallocate(a).expect("dealloc");
1175 pool.deallocate(b).expect("dealloc");
1176 assert_eq!(pool.peak_allocated, 3);
1178 }
1179
1180 #[test]
1183 fn block_pool_free_allocated_counts() {
1184 let config = PoolConfig {
1185 initial_blocks: 5,
1186 block_size: 256,
1187 max_blocks: 5,
1188 allow_growth: false,
1189 };
1190 let mut pool = TensorBlockPool::new(config);
1191
1192 let a = pool.allocate("a").expect("alloc");
1193 let _b = pool.allocate("b").expect("alloc");
1194 pool.reserve(2, "r").expect("reserve");
1195
1196 assert_eq!(pool.free_count(), 2); assert_eq!(pool.allocated_count(), 2); pool.deallocate(a).expect("dealloc");
1199 assert_eq!(pool.free_count(), 3);
1200 assert_eq!(pool.allocated_count(), 1);
1201 }
1202
1203 #[test]
1206 fn block_pool_stats_accuracy() {
1207 let config = PoolConfig {
1208 initial_blocks: 8,
1209 block_size: 512,
1210 max_blocks: 8,
1211 allow_growth: false,
1212 };
1213 let mut pool = TensorBlockPool::new(config);
1214
1215 let a = pool.allocate("a").expect("alloc");
1216 let _b = pool.allocate("b").expect("alloc");
1217 let _c = pool.allocate("c").expect("alloc");
1218 pool.reserve(3, "reserved_op").expect("reserve");
1219 pool.deallocate(a).expect("dealloc");
1220
1221 let stats = pool.block_stats();
1222 assert_eq!(stats.total_blocks, 8);
1223 assert_eq!(stats.free_blocks, 5); assert_eq!(stats.allocated_blocks, 2);
1225 assert_eq!(stats.reserved_blocks, 1);
1226 assert_eq!(stats.peak_allocated, 3);
1227 assert_eq!(stats.total_allocations, 3);
1228 assert_eq!(stats.total_deallocations, 1);
1229 assert!((stats.utilization - 0.25).abs() < f64::EPSILON);
1231 }
1232
1233 #[test]
1236 fn block_pool_defragment_moves_free_to_end() {
1237 let config = PoolConfig {
1238 initial_blocks: 4,
1239 block_size: 256,
1240 max_blocks: 4,
1241 allow_growth: false,
1242 };
1243 let mut pool = TensorBlockPool::new(config);
1244
1245 let id0 = pool.allocate("a").expect("alloc");
1247 let _id1 = pool.allocate("b").expect("alloc");
1248 let id2 = pool.allocate("c").expect("alloc");
1249 let _id3 = pool.allocate("d").expect("alloc");
1250 pool.deallocate(id0).expect("dealloc");
1251 pool.deallocate(id2).expect("dealloc");
1252
1253 let relocated = pool.defragment();
1254 assert!(relocated > 0);
1255
1256 let statuses: Vec<BlockStatus> = pool.blocks.iter().map(|b| b.status).collect();
1258 let first_free = statuses.iter().position(|s| *s == BlockStatus::Free);
1259 let last_alloc = statuses.iter().rposition(|s| *s == BlockStatus::Allocated);
1260 if let (Some(ff), Some(la)) = (first_free, last_alloc) {
1261 assert!(la < ff, "allocated blocks should precede free blocks");
1262 }
1263 }
1264
1265 #[test]
1266 fn block_pool_defragment_resets_generation() {
1267 let config = PoolConfig {
1268 initial_blocks: 2,
1269 block_size: 256,
1270 max_blocks: 2,
1271 allow_growth: false,
1272 };
1273 let mut pool = TensorBlockPool::new(config);
1274
1275 let id = pool.allocate("x").expect("alloc");
1276 pool.deallocate(id).expect("dealloc");
1277 assert_eq!(pool.get_block(id).expect("exists").generation, 1);
1279
1280 pool.defragment();
1281 for block in &pool.blocks {
1283 if block.status == BlockStatus::Free {
1284 assert_eq!(block.generation, 0);
1285 }
1286 }
1287 }
1288
1289 #[test]
1292 fn block_pool_shrink_to_fit_removes_trailing_free() {
1293 let config = PoolConfig {
1294 initial_blocks: 6,
1295 block_size: 256,
1296 max_blocks: 6,
1297 allow_growth: false,
1298 };
1299 let mut pool = TensorBlockPool::new(config);
1300
1301 let _a = pool.allocate("a").expect("alloc");
1303 let _b = pool.allocate("b").expect("alloc");
1304 let _c = pool.allocate("c").expect("alloc");
1305
1306 pool.defragment();
1308
1309 let removed = pool.shrink_to_fit();
1310 assert_eq!(removed, 3);
1311 assert_eq!(pool.blocks.len(), 3);
1312 }
1313
1314 #[test]
1315 fn block_pool_shrink_to_fit_no_trailing_free() {
1316 let config = PoolConfig {
1317 initial_blocks: 2,
1318 block_size: 256,
1319 max_blocks: 2,
1320 allow_growth: false,
1321 };
1322 let mut pool = TensorBlockPool::new(config);
1323
1324 let _a = pool.allocate("a").expect("alloc");
1325 let _b = pool.allocate("b").expect("alloc");
1326 let removed = pool.shrink_to_fit();
1327 assert_eq!(removed, 0);
1328 }
1329
1330 #[test]
1333 fn block_pool_get_block_existing() {
1334 let pool = TensorBlockPool::new(PoolConfig::default());
1335 let block = pool.get_block(0);
1336 assert!(block.is_some());
1337 assert_eq!(block.expect("exists").id, 0);
1338 }
1339
1340 #[test]
1341 fn block_pool_get_block_nonexistent() {
1342 let pool = TensorBlockPool::new(PoolConfig::default());
1343 assert!(pool.get_block(99999).is_none());
1344 }
1345
1346 #[test]
1349 fn block_pool_blocks_have_correct_size() {
1350 let config = PoolConfig {
1351 initial_blocks: 3,
1352 block_size: 2048,
1353 max_blocks: 10,
1354 allow_growth: true,
1355 };
1356 let pool = TensorBlockPool::new(config);
1357 for block in &pool.blocks {
1358 assert_eq!(block.size, 2048);
1359 }
1360 }
1361
1362 #[test]
1365 fn block_pool_grown_block_has_correct_size() {
1366 let config = PoolConfig {
1367 initial_blocks: 0,
1368 block_size: 8192,
1369 max_blocks: 5,
1370 allow_growth: true,
1371 };
1372 let mut pool = TensorBlockPool::new(config);
1373 let id = pool.allocate("grown").expect("alloc via growth");
1374 let block = pool.get_block(id).expect("exists");
1375 assert_eq!(block.size, 8192);
1376 }
1377}