Files
wifi-densepose/crates/ruvector-mincut/src/optimization/pool.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

648 lines
18 KiB
Rust

//! Pool Allocators and Lazy Level Deallocation
//!
//! Memory-efficient allocation strategies:
//! - Pool allocators for frequent allocations
//! - Lazy deallocation of unused j-tree levels
//! - Compact representations (u16 for small graphs)
//! - Demand-paged level materialization
//!
//! Target: 50-75% memory reduction
use crate::graph::VertexId;
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, RwLock};
/// Configuration for level pool
#[derive(Debug, Clone)]
pub struct PoolConfig {
/// Maximum number of materialized levels
pub max_materialized_levels: usize,
/// Eviction threshold (levels unused for this many operations)
pub eviction_threshold: u64,
/// Preallocation size for level data
pub prealloc_size: usize,
/// Enable lazy deallocation
pub lazy_dealloc: bool,
/// Memory budget in bytes (0 = unlimited)
pub memory_budget: usize,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
max_materialized_levels: 16,
eviction_threshold: 100,
prealloc_size: 1024,
lazy_dealloc: true,
memory_budget: 0,
}
}
}
/// Statistics for pool allocation
#[derive(Debug, Clone, Default)]
pub struct PoolStats {
/// Total allocations
pub allocations: u64,
/// Total deallocations
pub deallocations: u64,
/// Current pool size (bytes)
pub pool_size_bytes: usize,
/// Number of materialized levels
pub materialized_levels: usize,
/// Number of evictions
pub evictions: u64,
/// Peak memory usage (bytes)
pub peak_memory: usize,
}
/// State of a lazy level in the j-tree
#[derive(Debug, Clone)]
pub enum LazyLevel {
/// Level not yet materialized
Unmaterialized,
/// Level is materialized and valid
Materialized(LevelData),
/// Level is materialized but dirty (needs recomputation)
Dirty(LevelData),
/// Level was evicted (can be recomputed)
Evicted {
/// Last known vertex count (for preallocation)
last_vertex_count: usize,
},
}
impl LazyLevel {
/// Check if level is materialized
pub fn is_materialized(&self) -> bool {
matches!(self, LazyLevel::Materialized(_) | LazyLevel::Dirty(_))
}
/// Check if level needs recomputation
pub fn is_dirty(&self) -> bool {
matches!(self, LazyLevel::Dirty(_))
}
/// Get level data if materialized
pub fn data(&self) -> Option<&LevelData> {
match self {
LazyLevel::Materialized(data) | LazyLevel::Dirty(data) => Some(data),
_ => None,
}
}
/// Get mutable level data if materialized
pub fn data_mut(&mut self) -> Option<&mut LevelData> {
match self {
LazyLevel::Materialized(data) | LazyLevel::Dirty(data) => Some(data),
_ => None,
}
}
}
/// Data stored for a j-tree level
#[derive(Debug, Clone)]
pub struct LevelData {
/// Level index
pub level: usize,
/// Vertices in this level (compact representation)
pub vertices: Vec<u16>,
/// Adjacency list (compact)
pub adjacency: CompactAdjacency,
/// Cut value for this level
pub cut_value: f64,
/// Last access timestamp
last_access: u64,
/// Memory size in bytes
memory_size: usize,
}
impl LevelData {
/// Create new level data
pub fn new(level: usize, capacity: usize) -> Self {
Self {
level,
vertices: Vec::with_capacity(capacity),
adjacency: CompactAdjacency::new(capacity),
cut_value: f64::INFINITY,
last_access: 0,
memory_size: 0,
}
}
/// Update memory size estimate
pub fn update_memory_size(&mut self) {
self.memory_size =
self.vertices.len() * std::mem::size_of::<u16>() + self.adjacency.memory_size();
}
/// Get memory size
pub fn memory_size(&self) -> usize {
self.memory_size
}
}
/// Compact adjacency list using u16 vertex IDs
#[derive(Debug, Clone)]
pub struct CompactAdjacency {
/// Offset for each vertex into neighbors array
offsets: Vec<u32>,
/// Packed neighbors (vertex_id, weight as u16)
neighbors: Vec<(u16, u16)>,
}
impl CompactAdjacency {
/// Create new compact adjacency
pub fn new(capacity: usize) -> Self {
Self {
offsets: Vec::with_capacity(capacity + 1),
neighbors: Vec::new(),
}
}
/// Build from edge list
pub fn from_edges(edges: &[(u16, u16, u16)], num_vertices: usize) -> Self {
let mut adj: Vec<Vec<(u16, u16)>> = vec![Vec::new(); num_vertices];
for &(u, v, w) in edges {
adj[u as usize].push((v, w));
adj[v as usize].push((u, w));
}
let mut offsets = Vec::with_capacity(num_vertices + 1);
let mut neighbors = Vec::new();
offsets.push(0);
for vertex_neighbors in &adj {
neighbors.extend_from_slice(vertex_neighbors);
offsets.push(neighbors.len() as u32);
}
Self { offsets, neighbors }
}
/// Get neighbors of vertex
pub fn neighbors(&self, v: u16) -> &[(u16, u16)] {
let idx = v as usize;
if idx + 1 >= self.offsets.len() {
return &[];
}
let start = self.offsets[idx] as usize;
let end = self.offsets[idx + 1] as usize;
&self.neighbors[start..end]
}
/// Get degree of vertex
pub fn degree(&self, v: u16) -> usize {
let idx = v as usize;
if idx + 1 >= self.offsets.len() {
return 0;
}
(self.offsets[idx + 1] - self.offsets[idx]) as usize
}
/// Memory size in bytes
pub fn memory_size(&self) -> usize {
self.offsets.len() * std::mem::size_of::<u32>()
+ self.neighbors.len() * std::mem::size_of::<(u16, u16)>()
}
/// Number of vertices
pub fn num_vertices(&self) -> usize {
if self.offsets.is_empty() {
0
} else {
self.offsets.len() - 1
}
}
}
/// Pool allocator for j-tree levels
pub struct LevelPool {
config: PoolConfig,
/// Levels storage
levels: RwLock<HashMap<usize, LazyLevel>>,
/// LRU tracking
lru_order: RwLock<VecDeque<usize>>,
/// Operation counter
operation_counter: AtomicU64,
/// Current memory usage
memory_usage: AtomicUsize,
/// Statistics
allocations: AtomicU64,
deallocations: AtomicU64,
evictions: AtomicU64,
peak_memory: AtomicUsize,
/// Free list for reusable allocations
free_list: RwLock<Vec<LevelData>>,
}
impl LevelPool {
/// Create new level pool with default config
pub fn new() -> Self {
Self::with_config(PoolConfig::default())
}
/// Create with custom config
pub fn with_config(config: PoolConfig) -> Self {
Self {
config,
levels: RwLock::new(HashMap::new()),
lru_order: RwLock::new(VecDeque::new()),
operation_counter: AtomicU64::new(0),
memory_usage: AtomicUsize::new(0),
allocations: AtomicU64::new(0),
deallocations: AtomicU64::new(0),
evictions: AtomicU64::new(0),
peak_memory: AtomicUsize::new(0),
free_list: RwLock::new(Vec::new()),
}
}
/// Get or materialize a level
pub fn get_level(&self, level_idx: usize) -> Option<LazyLevel> {
self.touch(level_idx);
let levels = self.levels.read().unwrap();
levels.get(&level_idx).cloned()
}
/// Check if level is materialized
pub fn is_materialized(&self, level_idx: usize) -> bool {
let levels = self.levels.read().unwrap();
levels
.get(&level_idx)
.map(|l| l.is_materialized())
.unwrap_or(false)
}
/// Materialize a level with data
pub fn materialize(&self, level_idx: usize, data: LevelData) {
self.ensure_capacity();
let memory_size = data.memory_size();
self.memory_usage.fetch_add(memory_size, Ordering::Relaxed);
// Update peak memory
let current = self.memory_usage.load(Ordering::Relaxed);
let peak = self.peak_memory.load(Ordering::Relaxed);
if current > peak {
self.peak_memory.store(current, Ordering::Relaxed);
}
let mut levels = self.levels.write().unwrap();
levels.insert(level_idx, LazyLevel::Materialized(data));
let mut lru = self.lru_order.write().unwrap();
lru.retain(|&l| l != level_idx);
lru.push_back(level_idx);
self.allocations.fetch_add(1, Ordering::Relaxed);
}
/// Mark level as dirty
pub fn mark_dirty(&self, level_idx: usize) {
let mut levels = self.levels.write().unwrap();
if let Some(level) = levels.get_mut(&level_idx) {
if let LazyLevel::Materialized(data) = level.clone() {
*level = LazyLevel::Dirty(data);
}
}
}
/// Mark level as clean (after recomputation)
pub fn mark_clean(&self, level_idx: usize) {
let mut levels = self.levels.write().unwrap();
if let Some(level) = levels.get_mut(&level_idx) {
if let LazyLevel::Dirty(data) = level.clone() {
*level = LazyLevel::Materialized(data);
}
}
}
/// Evict a level (lazy deallocation)
pub fn evict(&self, level_idx: usize) {
let mut levels = self.levels.write().unwrap();
if let Some(level) = levels.get(&level_idx) {
let last_vertex_count = level.data().map(|d| d.vertices.len()).unwrap_or(0);
let memory_freed = level.data().map(|d| d.memory_size()).unwrap_or(0);
// Try to recycle the allocation
if self.config.lazy_dealloc {
if let Some(data) = level.data().cloned() {
let mut free_list = self.free_list.write().unwrap();
if free_list.len() < 10 {
free_list.push(data);
}
}
}
levels.insert(level_idx, LazyLevel::Evicted { last_vertex_count });
self.memory_usage.fetch_sub(memory_freed, Ordering::Relaxed);
self.evictions.fetch_add(1, Ordering::Relaxed);
self.deallocations.fetch_add(1, Ordering::Relaxed);
}
let mut lru = self.lru_order.write().unwrap();
lru.retain(|&l| l != level_idx);
}
/// Ensure we have capacity (evict if needed)
fn ensure_capacity(&self) {
let levels = self.levels.read().unwrap();
let materialized_count = levels.values().filter(|l| l.is_materialized()).count();
drop(levels);
if materialized_count >= self.config.max_materialized_levels {
// Evict least recently used
let lru = self.lru_order.read().unwrap();
if let Some(&evict_idx) = lru.front() {
drop(lru);
self.evict(evict_idx);
}
}
// Also check memory budget
if self.config.memory_budget > 0 {
while self.memory_usage.load(Ordering::Relaxed) > self.config.memory_budget {
let lru = self.lru_order.read().unwrap();
if let Some(&evict_idx) = lru.front() {
drop(lru);
self.evict(evict_idx);
} else {
break;
}
}
}
}
/// Update access timestamp for level
fn touch(&self, level_idx: usize) {
let timestamp = self.operation_counter.fetch_add(1, Ordering::Relaxed);
let mut levels = self.levels.write().unwrap();
if let Some(level) = levels.get_mut(&level_idx) {
if let Some(data) = level.data_mut() {
data.last_access = timestamp;
}
}
drop(levels);
// Update LRU order
let mut lru = self.lru_order.write().unwrap();
lru.retain(|&l| l != level_idx);
lru.push_back(level_idx);
}
/// Get a recycled allocation or create new
pub fn allocate_level(&self, level_idx: usize, capacity: usize) -> LevelData {
// Try to get from free list
let mut free_list = self.free_list.write().unwrap();
if let Some(mut data) = free_list.pop() {
data.level = level_idx;
data.vertices.clear();
data.cut_value = f64::INFINITY;
return data;
}
drop(free_list);
// Allocate new
LevelData::new(level_idx, capacity)
}
/// Get pool statistics
pub fn stats(&self) -> PoolStats {
let levels = self.levels.read().unwrap();
let materialized_count = levels.values().filter(|l| l.is_materialized()).count();
PoolStats {
allocations: self.allocations.load(Ordering::Relaxed),
deallocations: self.deallocations.load(Ordering::Relaxed),
pool_size_bytes: self.memory_usage.load(Ordering::Relaxed),
materialized_levels: materialized_count,
evictions: self.evictions.load(Ordering::Relaxed),
peak_memory: self.peak_memory.load(Ordering::Relaxed),
}
}
/// Get current memory usage in bytes
pub fn memory_usage(&self) -> usize {
self.memory_usage.load(Ordering::Relaxed)
}
/// Clear all levels
pub fn clear(&self) {
let mut levels = self.levels.write().unwrap();
levels.clear();
let mut lru = self.lru_order.write().unwrap();
lru.clear();
self.memory_usage.store(0, Ordering::Relaxed);
}
}
impl Default for LevelPool {
fn default() -> Self {
Self::new()
}
}
/// Vertex ID converter for compact representations
pub struct CompactVertexMapper {
/// Original vertex ID to compact ID
to_compact: HashMap<VertexId, u16>,
/// Compact ID to original vertex ID
to_original: Vec<VertexId>,
/// Next compact ID
next_id: u16,
}
impl CompactVertexMapper {
/// Create new mapper
pub fn new() -> Self {
Self {
to_compact: HashMap::new(),
to_original: Vec::new(),
next_id: 0,
}
}
/// Create from vertex list
pub fn from_vertices(vertices: &[VertexId]) -> Self {
let mut mapper = Self::new();
for &v in vertices {
mapper.get_or_insert(v);
}
mapper
}
/// Get compact ID, creating if needed
pub fn get_or_insert(&mut self, original: VertexId) -> u16 {
if let Some(&compact) = self.to_compact.get(&original) {
return compact;
}
let compact = self.next_id;
self.next_id += 1;
self.to_compact.insert(original, compact);
self.to_original.push(original);
compact
}
/// Get compact ID if exists
pub fn get(&self, original: VertexId) -> Option<u16> {
self.to_compact.get(&original).copied()
}
/// Get original vertex ID from compact
pub fn to_original(&self, compact: u16) -> Option<VertexId> {
self.to_original.get(compact as usize).copied()
}
/// Number of mapped vertices
pub fn len(&self) -> usize {
self.to_original.len()
}
/// Check if empty
pub fn is_empty(&self) -> bool {
self.to_original.is_empty()
}
}
impl Default for CompactVertexMapper {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lazy_level_states() {
let level = LazyLevel::Unmaterialized;
assert!(!level.is_materialized());
let data = LevelData::new(0, 100);
let level = LazyLevel::Materialized(data.clone());
assert!(level.is_materialized());
assert!(!level.is_dirty());
let level = LazyLevel::Dirty(data);
assert!(level.is_materialized());
assert!(level.is_dirty());
}
#[test]
fn test_compact_adjacency() {
let edges = vec![(0u16, 1u16, 10u16), (1, 2, 20), (2, 0, 30)];
let adj = CompactAdjacency::from_edges(&edges, 3);
assert_eq!(adj.num_vertices(), 3);
assert_eq!(adj.degree(0), 2);
assert_eq!(adj.degree(1), 2);
assert_eq!(adj.degree(2), 2);
}
#[test]
fn test_level_pool_materialize() {
let pool = LevelPool::new();
let data = LevelData::new(0, 100);
pool.materialize(0, data);
assert!(pool.is_materialized(0));
assert!(!pool.is_materialized(1));
}
#[test]
fn test_level_pool_eviction() {
let pool = LevelPool::with_config(PoolConfig {
max_materialized_levels: 2,
..Default::default()
});
pool.materialize(0, LevelData::new(0, 100));
pool.materialize(1, LevelData::new(1, 100));
assert!(pool.is_materialized(0));
assert!(pool.is_materialized(1));
// This should evict level 0
pool.materialize(2, LevelData::new(2, 100));
assert!(!pool.is_materialized(0));
assert!(pool.is_materialized(1));
assert!(pool.is_materialized(2));
}
#[test]
fn test_level_pool_dirty() {
let pool = LevelPool::new();
let data = LevelData::new(0, 100);
pool.materialize(0, data);
pool.mark_dirty(0);
if let Some(LazyLevel::Dirty(_)) = pool.get_level(0) {
// OK
} else {
panic!("Level should be dirty");
}
pool.mark_clean(0);
if let Some(LazyLevel::Materialized(_)) = pool.get_level(0) {
// OK
} else {
panic!("Level should be clean");
}
}
#[test]
fn test_compact_vertex_mapper() {
let mut mapper = CompactVertexMapper::new();
let c1 = mapper.get_or_insert(100);
let c2 = mapper.get_or_insert(200);
let c3 = mapper.get_or_insert(100); // Should return same as c1
assert_eq!(c1, 0);
assert_eq!(c2, 1);
assert_eq!(c3, 0);
assert_eq!(mapper.to_original(c1), Some(100));
assert_eq!(mapper.to_original(c2), Some(200));
}
#[test]
fn test_pool_stats() {
let pool = LevelPool::new();
let data = LevelData::new(0, 100);
pool.materialize(0, data);
let stats = pool.stats();
assert_eq!(stats.allocations, 1);
assert_eq!(stats.materialized_levels, 1);
}
#[test]
fn test_level_data_memory_size() {
let mut data = LevelData::new(0, 100);
data.vertices = vec![0, 1, 2, 3, 4];
data.update_memory_size();
assert!(data.memory_size() > 0);
}
}