Skip to main content

rapidgeo_distance/format_batch/
buffer_pool.rs

1use crate::formats::CoordSource;
2use crate::LngLat;
3
4#[cfg(feature = "batch")]
5use super::parallel;
6use super::sequential::{pairwise_haversine_any_extend, pairwise_haversine_iter_extend};
7
8/// Buffer pool for coordinate processing operations.
9///
10/// Manages a pool of reusable `Vec<f64>` buffers to minimize memory allocations
11/// during repeated coordinate calculations. This is particularly beneficial for
12/// iterative processing of large datasets or real-time applications.
13///
14/// # Performance Benefits
15///
16/// - **Reduced allocations**: Reuses buffers instead of allocating new ones
17/// - **Memory locality**: Keeps buffer capacity to avoid repeated growth
18/// - **Pool management**: Limits pool size to prevent unbounded memory growth
19/// - **RAII safety**: Automatic buffer return via scoped operations
20///
21/// # Usage Patterns
22///
23/// The pool supports two usage patterns:
24/// 1. **Manual management**: `get_buffer()` and `return_buffer()`
25/// 2. **Scoped operations**: `with_buffer()` for automatic lifecycle management
26///
27/// # Examples
28///
29/// ```
30/// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
31/// use rapidgeo_distance::LngLat;
32///
33/// // Create pool with initial buffer capacity of 1000 elements
34/// let mut pool = BufferPool::new(1000);
35///
36/// // Scoped operation (recommended)
37/// let result = pool.with_buffer(|buffer| {
38///     // Use buffer for calculations
39///     buffer.extend([1.0, 2.0, 3.0]);
40///     buffer.len()
41/// }); // Buffer automatically returned to pool
42///
43/// assert_eq!(result, 3);
44/// assert_eq!(pool.pool_size(), 1); // Buffer was returned
45/// ```
46///
47/// # Memory Management
48///
49/// - Buffers are cleared (length set to 0) when returned, but capacity is preserved
50/// - Pool size is capped to prevent unbounded growth
51/// - Dropped buffers are not returned to the pool once capacity is reached
52///
53/// # Thread Safety
54///
55/// This pool is **not** thread-safe. Use separate pools per thread or add
56/// synchronization for concurrent access.
57///
58/// # See Also
59///
60/// - [`with_buffer`](BufferPool::with_buffer) for scoped buffer operations
61/// - [`pairwise_haversine_any`](BufferPool::pairwise_haversine_any) for coordinate-specific operations
62pub struct BufferPool {
63    buffers: Vec<Vec<f64>>,
64    initial_capacity: usize,
65    max_pool_size: usize,
66}
67
68impl BufferPool {
69    /// Creates a new buffer pool with the specified initial buffer capacity.
70    ///
71    /// # Arguments
72    ///
73    /// * `initial_capacity` - The initial capacity (in elements) for new buffers
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
79    ///
80    /// // Pool for processing up to 1000 coordinate pairs
81    /// let pool = BufferPool::new(1000);
82    /// assert_eq!(pool.pool_size(), 0); // No buffers initially
83    /// ```
84    ///
85    /// # Default Settings
86    ///
87    /// - **Maximum pool size**: 8 buffers
88    /// - **Initial pool size**: 0 buffers (created on demand)
89    pub fn new(initial_capacity: usize) -> Self {
90        Self {
91            buffers: Vec::new(),
92            initial_capacity,
93            max_pool_size: 8,
94        }
95    }
96
97    /// Creates a new buffer pool with custom capacity and pool size limits.
98    ///
99    /// # Arguments
100    ///
101    /// * `initial_capacity` - The initial capacity (in elements) for new buffers
102    /// * `max_pool_size` - Maximum number of buffers to keep in the pool
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
108    ///
109    /// // Pool for memory-constrained environments
110    /// let pool = BufferPool::with_max_size(500, 4);
111    /// assert_eq!(pool.pool_size(), 0);
112    /// ```
113    ///
114    /// # Pool Size Considerations
115    ///
116    /// - **Small pools (1-4)**: Lower memory usage, more allocations
117    /// - **Large pools (8-16)**: Higher memory usage, fewer allocations
118    /// - **Very large pools (>16)**: Diminishing returns, potential memory waste
119    pub fn with_max_size(initial_capacity: usize, max_pool_size: usize) -> Self {
120        Self {
121            buffers: Vec::new(),
122            initial_capacity,
123            max_pool_size,
124        }
125    }
126
127    /// Gets a buffer from the pool, creating a new one if the pool is empty.
128    ///
129    /// The returned buffer is empty (length 0) but may have existing capacity
130    /// from previous use. You must call [`return_buffer`](Self::return_buffer)
131    /// when finished to return it to the pool.
132    ///
133    /// # Returns
134    ///
135    /// An empty `Vec<f64>` ready for use
136    ///
137    /// # Examples
138    ///
139    /// ```
140    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
141    ///
142    /// let mut pool = BufferPool::new(100);
143    ///
144    /// let mut buffer = pool.get_buffer();
145    /// assert_eq!(buffer.len(), 0);
146    /// assert!(buffer.capacity() >= 100);
147    ///
148    /// buffer.push(42.0);
149    /// pool.return_buffer(buffer);
150    /// ```
151    ///
152    /// # Performance Notes
153    ///
154    /// - Reused buffers retain their capacity from previous use
155    /// - New buffers are allocated with the pool's initial capacity
156    /// - Consider using [`with_buffer`](Self::with_buffer) for automatic management
157    pub fn get_buffer(&mut self) -> Vec<f64> {
158        self.buffers
159            .pop()
160            .unwrap_or_else(|| Vec::with_capacity(self.initial_capacity))
161    }
162
163    /// Returns a buffer to the pool for reuse.
164    ///
165    /// The buffer is cleared (length set to 0) but capacity is preserved.
166    /// If the pool is full, the buffer is dropped instead of being stored.
167    ///
168    /// # Arguments
169    ///
170    /// * `buffer` - The buffer to return (will be cleared)
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
176    ///
177    /// let mut pool = BufferPool::new(50);
178    ///
179    /// let mut buffer = pool.get_buffer();
180    /// buffer.extend([1.0, 2.0, 3.0]);
181    ///
182    /// pool.return_buffer(buffer);
183    /// assert_eq!(pool.pool_size(), 1);
184    ///
185    /// // Buffer is cleared but capacity preserved
186    /// let buffer2 = pool.get_buffer();
187    /// assert_eq!(buffer2.len(), 0);
188    /// ```
189    ///
190    /// # Pool Capacity
191    ///
192    /// Buffers are only stored if there's room in the pool:
193    ///
194    /// ```
195    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
196    ///
197    /// let mut pool = BufferPool::with_max_size(50, 2); // Max 2 buffers
198    ///
199    /// let buf1 = pool.get_buffer();
200    /// let buf2 = pool.get_buffer();
201    /// pool.return_buffer(buf1);
202    /// pool.return_buffer(buf2);
203    /// assert_eq!(pool.pool_size(), 2);
204    ///
205    /// // Third buffer is dropped, not stored
206    /// let buf3 = pool.get_buffer();
207    /// pool.return_buffer(buf3);
208    /// assert_eq!(pool.pool_size(), 2); // Still 2
209    /// ```
210    pub fn return_buffer(&mut self, mut buffer: Vec<f64>) {
211        if self.buffers.len() < self.max_pool_size {
212            buffer.clear();
213            self.buffers.push(buffer);
214        }
215    }
216
217    /// Executes a closure with a temporary buffer, automatically managing its lifecycle.
218    ///
219    /// This is the recommended way to use the buffer pool as it ensures the buffer
220    /// is always returned, even if the closure panics or returns early.
221    ///
222    /// # Arguments
223    ///
224    /// * `f` - Closure that receives a mutable buffer reference
225    ///
226    /// # Returns
227    ///
228    /// The result of the closure
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
234    ///
235    /// let mut pool = BufferPool::new(100);
236    ///
237    /// let sum = pool.with_buffer(|buffer| {
238    ///     buffer.extend([1.0, 2.0, 3.0, 4.0, 5.0]);
239    ///     buffer.iter().sum::<f64>()
240    /// });
241    ///
242    /// assert_eq!(sum, 15.0);
243    /// assert_eq!(pool.pool_size(), 1); // Buffer was returned
244    /// ```
245    ///
246    /// # Error Safety
247    ///
248    /// The buffer is returned to the pool even if the closure panics:
249    ///
250    /// ```should_panic
251    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
252    ///
253    /// let mut pool = BufferPool::new(100);
254    ///
255    /// pool.with_buffer(|_buffer| {
256    ///     panic!("Something went wrong!");
257    /// });
258    /// ```
259    ///
260    /// # Performance Benefits
261    ///
262    /// - **No manual tracking**: Impossible to forget buffer return
263    /// - **Exception safety**: Buffer returned even on panic
264    /// - **Zero overhead**: Inlined closure execution
265    pub fn with_buffer<F, R>(&mut self, f: F) -> R
266    where
267        F: FnOnce(&mut Vec<f64>) -> R,
268    {
269        let mut buffer = self.get_buffer();
270        let result = f(&mut buffer);
271        self.return_buffer(buffer);
272        result
273    }
274
275    /// Computes pairwise Haversine distances using a pooled buffer.
276    ///
277    /// Calculates the distance between consecutive coordinate pairs using the
278    /// Haversine formula. The result buffer is obtained from the pool but
279    /// **not** returned automatically - you own the returned vector.
280    ///
281    /// # Arguments
282    ///
283    /// * `iter` - Iterator over `LngLat` coordinates
284    ///
285    /// # Returns
286    ///
287    /// Vector of distances in meters between consecutive coordinate pairs
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
293    /// use rapidgeo_distance::LngLat;
294    ///
295    /// let mut pool = BufferPool::new(100);
296    ///
297    /// let coords = [
298    ///     LngLat::new_deg(-122.4194, 37.7749), // San Francisco
299    ///     LngLat::new_deg(-74.0060, 40.7128),  // New York
300    ///     LngLat::new_deg(-87.6298, 41.8781),  // Chicago
301    /// ];
302    ///
303    /// let distances = pool.pairwise_haversine_iter(coords.iter().copied());
304    /// assert_eq!(distances.len(), 2); // n-1 distances for n points
305    ///
306    /// // SF to NYC is approximately 4100km
307    /// assert!(distances[0] > 4_000_000.0 && distances[0] < 4_200_000.0);
308    /// ```
309    ///
310    /// # Performance
311    ///
312    /// - **Buffer reuse**: Uses pooled buffer for intermediate calculations
313    /// - **Single allocation**: Result vector allocated once with appropriate capacity
314    /// - **Lazy evaluation**: Iterator is consumed on-demand
315    ///
316    /// # See Also
317    ///
318    /// - [`pairwise_haversine_any`](Self::pairwise_haversine_any) for `CoordSource` input
319    /// - [`pairwise_haversine_iter`](super::sequential::pairwise_haversine_iter) for non-pooled version
320    pub fn pairwise_haversine_iter<I>(&mut self, iter: I) -> Vec<f64>
321    where
322        I: Iterator<Item = LngLat>,
323    {
324        let mut result = self.get_buffer();
325        pairwise_haversine_iter_extend(iter, &mut result);
326        result
327    }
328
329    /// Computes pairwise Haversine distances from any coordinate source using a pooled buffer.
330    ///
331    /// Accepts any type implementing [`CoordSource`] (tuples, arrays, etc.) and computes
332    /// distances between consecutive coordinates. Automatically handles format detection
333    /// and conversion as needed.
334    ///
335    /// # Arguments
336    ///
337    /// * `coords` - Any coordinate source (Vec<LngLat>, Vec<(f64,f64)>, Vec<f64>, etc.)
338    ///
339    /// # Returns
340    ///
341    /// Vector of distances in meters between consecutive coordinate pairs
342    ///
343    /// # Examples
344    ///
345    /// ```
346    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
347    /// use rapidgeo_distance::LngLat;
348    ///
349    /// let mut pool = BufferPool::new(100);
350    ///
351    /// // Works with various coordinate formats
352    /// let coords_lnglat = vec![
353    ///     LngLat::new_deg(-122.4194, 37.7749),
354    ///     LngLat::new_deg(-74.0060, 40.7128),
355    /// ];
356    /// let distances1 = pool.pairwise_haversine_any(&coords_lnglat);
357    ///
358    /// let coords_tuples = vec![
359    ///     (-122.4194, 37.7749),
360    ///     (-74.0060, 40.7128),
361    /// ];
362    /// let distances2 = pool.pairwise_haversine_any(&coords_tuples);
363    ///
364    /// // Results should be identical
365    /// assert!((distances1[0] - distances2[0]).abs() < 1e-10);
366    /// ```
367    ///
368    /// # Format Support
369    ///
370    /// Supports all coordinate formats:
371    /// - `Vec<LngLat>` - Native format
372    /// - `Vec<(f64, f64)>` - Tuples with format detection
373    /// - `Vec<f64>` - Flat arrays (chunked into pairs)
374    /// - `&[f64]` - Array slices
375    ///
376    /// # See Also
377    ///
378    /// - [`pairwise_haversine_iter`](Self::pairwise_haversine_iter) for iterator input
379    /// - [`CoordSource`] trait for supported input types
380    pub fn pairwise_haversine_any<T: CoordSource>(&mut self, coords: &T) -> Vec<f64> {
381        let mut result = self.get_buffer();
382        pairwise_haversine_any_extend(coords, &mut result);
383        result
384    }
385
386    #[cfg(feature = "batch")]
387    pub fn pairwise_haversine_par_iter<I>(&mut self, iter: I) -> Vec<f64>
388    where
389        I: Iterator<Item = LngLat>,
390    {
391        let mut result = self.get_buffer();
392        parallel::pairwise_haversine_par_iter_extend(iter, &mut result);
393        result
394    }
395
396    #[cfg(feature = "batch")]
397    pub fn pairwise_haversine_par_any<T: CoordSource + Sync>(&mut self, coords: &T) -> Vec<f64> {
398        let mut result = self.get_buffer();
399        parallel::pairwise_haversine_par_any_extend(coords, &mut result);
400        result
401    }
402
403    /// Returns the number of buffers currently stored in the pool.
404    ///
405    /// This count represents available buffers ready for reuse. It will be
406    /// between 0 and the maximum pool size configured during construction.
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
412    ///
413    /// let mut pool = BufferPool::new(100);
414    /// assert_eq!(pool.pool_size(), 0); // Initially empty
415    ///
416    /// let buffer = pool.get_buffer();
417    /// assert_eq!(pool.pool_size(), 0); // Buffer checked out
418    ///
419    /// pool.return_buffer(buffer);
420    /// assert_eq!(pool.pool_size(), 1); // Buffer returned
421    /// ```
422    ///
423    /// # Use Cases
424    ///
425    /// - **Debugging**: Verify buffers are being returned properly
426    /// - **Monitoring**: Track pool utilization in long-running applications
427    /// - **Testing**: Ensure proper resource management in tests
428    pub fn pool_size(&self) -> usize {
429        self.buffers.len()
430    }
431
432    /// Removes all buffers from the pool, freeing their memory.
433    ///
434    /// This is useful for releasing memory when the pool won't be used for
435    /// an extended period, or for cleanup in tests and benchmarks.
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// use rapidgeo_distance::format_batch::buffer_pool::BufferPool;
441    ///
442    /// let mut pool = BufferPool::new(100);
443    ///
444    /// // Use some buffers
445    /// let buf1 = pool.get_buffer();
446    /// let buf2 = pool.get_buffer();
447    /// pool.return_buffer(buf1);
448    /// pool.return_buffer(buf2);
449    /// assert_eq!(pool.pool_size(), 2);
450    ///
451    /// // Clear all buffers
452    /// pool.clear_pool();
453    /// assert_eq!(pool.pool_size(), 0);
454    /// ```
455    ///
456    /// # Memory Impact
457    ///
458    /// After clearing, subsequent `get_buffer()` calls will allocate new buffers
459    /// with the pool's configured initial capacity. This may cause temporary
460    /// performance degradation until the pool is rebuilt.
461    ///
462    /// # Use Cases
463    ///
464    /// - **Memory pressure**: Free memory when pool is idle
465    /// - **Test cleanup**: Reset pool state between test cases
466    /// - **Capacity changes**: Clear before changing buffer sizing strategy
467    pub fn clear_pool(&mut self) {
468        self.buffers.clear();
469    }
470}