ruvector_mincut/pool/
mod.rs

1//! Memory pools for BFS and graph traversal allocations
2//!
3//! Provides reusable memory pools to reduce allocation overhead during
4//! repeated BFS/DFS operations in minimum cut algorithms.
5//!
6//! # Overview
7//!
8//! Graph algorithms like BFS perform many allocations:
9//! - Queue for vertices to visit
10//! - HashSet/BitSet for visited vertices
11//! - Vec for collecting results
12//!
13//! By reusing these structures, we avoid repeated allocation/deallocation
14//! overhead, which can be significant for algorithms that perform many
15//! traversals.
16//!
17//! # Thread Safety
18//!
19//! The pools use thread-local storage for zero-contention access.
20//! Each thread gets its own pool of resources.
21//!
22//! # Example
23//!
24//! ```
25//! use ruvector_mincut::pool::BfsPool;
26//!
27//! // Acquire resources from the pool
28//! let mut resources = BfsPool::acquire(256);
29//!
30//! // Use resources for BFS
31//! resources.queue.push_back(0);
32//! resources.visited.insert(0);
33//!
34//! // Clear and return to pool automatically when dropped
35//! drop(resources);
36//! ```
37
38use crate::graph::VertexId;
39use std::collections::{HashSet, VecDeque};
40use std::cell::RefCell;
41
42/// Thread-local pool for BFS resources
43thread_local! {
44    static BFS_POOL: RefCell<BfsPoolInner> = RefCell::new(BfsPoolInner::new());
45}
46
47/// Inner pool state
48struct BfsPoolInner {
49    /// Pool of reusable queues
50    queues: Vec<VecDeque<VertexId>>,
51    /// Pool of reusable visited sets
52    visited_sets: Vec<HashSet<VertexId>>,
53    /// Pool of reusable result vectors
54    result_vecs: Vec<Vec<VertexId>>,
55    /// Statistics: number of acquires
56    acquires: usize,
57    /// Statistics: number of hits (reused from pool)
58    hits: usize,
59}
60
61impl BfsPoolInner {
62    fn new() -> Self {
63        Self {
64            queues: Vec::new(),
65            visited_sets: Vec::new(),
66            result_vecs: Vec::new(),
67            acquires: 0,
68            hits: 0,
69        }
70    }
71
72    fn acquire_queue(&mut self, capacity: usize) -> VecDeque<VertexId> {
73        self.acquires += 1;
74        if let Some(mut queue) = self.queues.pop() {
75            self.hits += 1;
76            queue.clear();
77            // Reserve if needed
78            if queue.capacity() < capacity {
79                queue.reserve(capacity - queue.len());
80            }
81            queue
82        } else {
83            VecDeque::with_capacity(capacity)
84        }
85    }
86
87    fn acquire_visited(&mut self, capacity: usize) -> HashSet<VertexId> {
88        self.acquires += 1;
89        if let Some(mut set) = self.visited_sets.pop() {
90            self.hits += 1;
91            set.clear();
92            if set.capacity() < capacity {
93                set.reserve(capacity - set.len());
94            }
95            set
96        } else {
97            HashSet::with_capacity(capacity)
98        }
99    }
100
101    fn acquire_vec(&mut self, capacity: usize) -> Vec<VertexId> {
102        self.acquires += 1;
103        if let Some(mut v) = self.result_vecs.pop() {
104            self.hits += 1;
105            v.clear();
106            if v.capacity() < capacity {
107                v.reserve(capacity - v.len());
108            }
109            v
110        } else {
111            Vec::with_capacity(capacity)
112        }
113    }
114
115    fn return_queue(&mut self, queue: VecDeque<VertexId>) {
116        // Keep at most 8 pooled queues
117        if self.queues.len() < 8 {
118            self.queues.push(queue);
119        }
120    }
121
122    fn return_visited(&mut self, set: HashSet<VertexId>) {
123        if self.visited_sets.len() < 8 {
124            self.visited_sets.push(set);
125        }
126    }
127
128    fn return_vec(&mut self, v: Vec<VertexId>) {
129        if self.result_vecs.len() < 8 {
130            self.result_vecs.push(v);
131        }
132    }
133}
134
135/// BFS resources acquired from the pool
136///
137/// Automatically returns resources to the pool when dropped.
138pub struct BfsResources {
139    /// Queue for BFS traversal
140    pub queue: VecDeque<VertexId>,
141    /// Set of visited vertices
142    pub visited: HashSet<VertexId>,
143    /// Vector for collecting results
144    pub results: Vec<VertexId>,
145}
146
147impl Drop for BfsResources {
148    fn drop(&mut self) {
149        // Return resources to pool
150        BFS_POOL.with(|pool| {
151            let mut pool = pool.borrow_mut();
152
153            // Take ownership via swap
154            let queue = std::mem::take(&mut self.queue);
155            let visited = std::mem::take(&mut self.visited);
156            let results = std::mem::take(&mut self.results);
157
158            pool.return_queue(queue);
159            pool.return_visited(visited);
160            pool.return_vec(results);
161        });
162    }
163}
164
165/// Pool for BFS memory allocation
166///
167/// Provides thread-local pools for reusing BFS data structures.
168pub struct BfsPool;
169
170impl BfsPool {
171    /// Acquire BFS resources from the pool
172    ///
173    /// # Arguments
174    ///
175    /// * `expected_size` - Expected number of vertices to visit
176    ///
177    /// # Returns
178    ///
179    /// BfsResources that will be returned to the pool when dropped
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use ruvector_mincut::pool::BfsPool;
185    ///
186    /// let mut res = BfsPool::acquire(100);
187    ///
188    /// // Perform BFS
189    /// res.queue.push_back(0);
190    /// while let Some(v) = res.queue.pop_front() {
191    ///     if res.visited.insert(v) {
192    ///         res.results.push(v);
193    ///         // Push neighbors...
194    ///     }
195    /// }
196    ///
197    /// // Resources automatically returned when res is dropped
198    /// ```
199    pub fn acquire(expected_size: usize) -> BfsResources {
200        BFS_POOL.with(|pool| {
201            let mut pool = pool.borrow_mut();
202            BfsResources {
203                queue: pool.acquire_queue(expected_size),
204                visited: pool.acquire_visited(expected_size),
205                results: pool.acquire_vec(expected_size),
206            }
207        })
208    }
209
210    /// Get pool statistics for the current thread
211    ///
212    /// Returns (acquires, hits, hit_rate)
213    pub fn stats() -> (usize, usize, f64) {
214        BFS_POOL.with(|pool| {
215            let pool = pool.borrow();
216            let rate = if pool.acquires > 0 {
217                pool.hits as f64 / pool.acquires as f64
218            } else {
219                0.0
220            };
221            (pool.acquires, pool.hits, rate)
222        })
223    }
224
225    /// Clear the pool (useful for testing or memory pressure)
226    pub fn clear() {
227        BFS_POOL.with(|pool| {
228            let mut pool = pool.borrow_mut();
229            pool.queues.clear();
230            pool.visited_sets.clear();
231            pool.result_vecs.clear();
232        });
233    }
234}
235
236/// Pool for distance-annotated BFS
237pub struct DistanceBfsResources {
238    /// Queue with (vertex, distance) pairs
239    pub queue: VecDeque<(VertexId, usize)>,
240    /// Set of visited vertices
241    pub visited: HashSet<VertexId>,
242    /// Distance map
243    pub distances: std::collections::HashMap<VertexId, usize>,
244}
245
246impl Default for DistanceBfsResources {
247    fn default() -> Self {
248        Self::new()
249    }
250}
251
252impl DistanceBfsResources {
253    /// Create new distance BFS resources
254    pub fn new() -> Self {
255        Self {
256            queue: VecDeque::new(),
257            visited: HashSet::new(),
258            distances: std::collections::HashMap::new(),
259        }
260    }
261
262    /// Create with capacity
263    pub fn with_capacity(capacity: usize) -> Self {
264        Self {
265            queue: VecDeque::with_capacity(capacity),
266            visited: HashSet::with_capacity(capacity),
267            distances: std::collections::HashMap::with_capacity(capacity),
268        }
269    }
270
271    /// Clear all resources for reuse
272    pub fn clear(&mut self) {
273        self.queue.clear();
274        self.visited.clear();
275        self.distances.clear();
276    }
277
278    /// Perform BFS from a source vertex
279    ///
280    /// Returns the set of vertices reachable within the given radius.
281    ///
282    /// # Arguments
283    ///
284    /// * `source` - Starting vertex
285    /// * `radius` - Maximum distance to traverse
286    /// * `adjacency` - Function to get neighbors of a vertex
287    pub fn bfs_within_radius<F>(
288        &mut self,
289        source: VertexId,
290        radius: usize,
291        adjacency: F,
292    ) -> &HashSet<VertexId>
293    where
294        F: Fn(VertexId) -> Vec<VertexId>,
295    {
296        self.clear();
297
298        self.queue.push_back((source, 0));
299        self.visited.insert(source);
300        self.distances.insert(source, 0);
301
302        while let Some((vertex, dist)) = self.queue.pop_front() {
303            if dist >= radius {
304                continue;
305            }
306
307            for neighbor in adjacency(vertex) {
308                if self.visited.insert(neighbor) {
309                    let new_dist = dist + 1;
310                    self.distances.insert(neighbor, new_dist);
311                    self.queue.push_back((neighbor, new_dist));
312                }
313            }
314        }
315
316        &self.visited
317    }
318}
319
320/// Compact bitset pool for small graphs
321///
322/// Uses fixed-size bitsets instead of HashSets for graphs with <= 256 vertices.
323pub struct CompactBfsResources {
324    /// Queue for BFS traversal
325    pub queue: VecDeque<VertexId>,
326    /// Visited bitmap (256 bits = 32 bytes)
327    pub visited: [u64; 4],
328    /// Results vector
329    pub results: Vec<VertexId>,
330}
331
332impl Default for CompactBfsResources {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338impl CompactBfsResources {
339    /// Create new compact BFS resources
340    pub fn new() -> Self {
341        Self {
342            queue: VecDeque::with_capacity(32),
343            visited: [0; 4],
344            results: Vec::with_capacity(32),
345        }
346    }
347
348    /// Clear for reuse
349    pub fn clear(&mut self) {
350        self.queue.clear();
351        self.visited = [0; 4];
352        self.results.clear();
353    }
354
355    /// Check if vertex is visited
356    #[inline]
357    pub fn is_visited(&self, v: VertexId) -> bool {
358        if v >= 256 {
359            return false;
360        }
361        let idx = (v / 64) as usize;
362        let bit = v % 64;
363        (self.visited[idx] & (1u64 << bit)) != 0
364    }
365
366    /// Mark vertex as visited
367    #[inline]
368    pub fn mark_visited(&mut self, v: VertexId) -> bool {
369        if v >= 256 {
370            return false;
371        }
372        let idx = (v / 64) as usize;
373        let bit = v % 64;
374        let was_visited = (self.visited[idx] & (1u64 << bit)) != 0;
375        self.visited[idx] |= 1u64 << bit;
376        !was_visited
377    }
378
379    /// Count visited vertices
380    pub fn visited_count(&self) -> usize {
381        self.visited.iter().map(|w| w.count_ones() as usize).sum()
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    #[test]
390    fn test_bfs_pool_acquire() {
391        let res = BfsPool::acquire(100);
392        assert!(res.queue.is_empty());
393        assert!(res.visited.is_empty());
394        assert!(res.results.is_empty());
395    }
396
397    #[test]
398    fn test_bfs_pool_reuse() {
399        // First acquire
400        {
401            let mut res = BfsPool::acquire(100);
402            res.queue.push_back(1);
403            res.queue.push_back(2);
404            res.visited.insert(1);
405            res.visited.insert(2);
406        } // Returned to pool
407
408        // Second acquire should get cleared resources
409        let res = BfsPool::acquire(100);
410        assert!(res.queue.is_empty());
411        assert!(res.visited.is_empty());
412    }
413
414    #[test]
415    fn test_bfs_pool_stats() {
416        BfsPool::clear(); // Reset stats
417
418        // Multiple acquires
419        let _r1 = BfsPool::acquire(10);
420        let _r2 = BfsPool::acquire(10);
421        drop(_r1);
422        drop(_r2);
423
424        // Third acquire should hit cache
425        let _r3 = BfsPool::acquire(10);
426
427        let (acquires, hits, _rate) = BfsPool::stats();
428        assert!(acquires >= 3);
429        assert!(hits >= 1); // At least one hit
430    }
431
432    #[test]
433    fn test_distance_bfs() {
434        let mut res = DistanceBfsResources::with_capacity(10);
435
436        // Linear graph: 0 - 1 - 2 - 3 - 4
437        let adjacency = |v: VertexId| -> Vec<VertexId> {
438            match v {
439                0 => vec![1],
440                1 => vec![0, 2],
441                2 => vec![1, 3],
442                3 => vec![2, 4],
443                4 => vec![3],
444                _ => vec![],
445            }
446        };
447
448        let visited = res.bfs_within_radius(0, 2, adjacency);
449
450        // Should reach 0, 1, 2 (radius 2 from 0)
451        assert!(visited.contains(&0));
452        assert!(visited.contains(&1));
453        assert!(visited.contains(&2));
454        assert!(!visited.contains(&3)); // Beyond radius
455        assert!(!visited.contains(&4));
456
457        // Check distances
458        assert_eq!(res.distances.get(&0), Some(&0));
459        assert_eq!(res.distances.get(&1), Some(&1));
460        assert_eq!(res.distances.get(&2), Some(&2));
461    }
462
463    #[test]
464    fn test_compact_bfs() {
465        let mut res = CompactBfsResources::new();
466
467        assert!(!res.is_visited(0));
468        assert!(res.mark_visited(0)); // First visit returns true
469        assert!(res.is_visited(0));
470        assert!(!res.mark_visited(0)); // Second visit returns false
471
472        res.mark_visited(100);
473        res.mark_visited(255);
474
475        assert_eq!(res.visited_count(), 3);
476
477        res.clear();
478        assert_eq!(res.visited_count(), 0);
479    }
480
481    #[test]
482    fn test_compact_bfs_boundary() {
483        let mut res = CompactBfsResources::new();
484
485        // Test boundary vertices
486        assert!(res.mark_visited(0));
487        assert!(res.mark_visited(63));
488        assert!(res.mark_visited(64));
489        assert!(res.mark_visited(127));
490        assert!(res.mark_visited(128));
491        assert!(res.mark_visited(191));
492        assert!(res.mark_visited(192));
493        assert!(res.mark_visited(255));
494
495        assert!(res.is_visited(0));
496        assert!(res.is_visited(255));
497
498        // Out of range
499        assert!(!res.is_visited(256));
500        assert!(!res.mark_visited(256));
501    }
502}