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}