Skip to content

MichaelDuPlessis/vecpool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vecpool

Crates.io Documentation License

A thread-local pool of reusable Vec buffers with cross-type reuse.

Why?

Allocating and deallocating Vecs in a loop is expensive. vecpool recycles the underlying heap buffers so subsequent allocations are free — just a pointer pop from a thread-local stack.

Unlike per-type pools, vecpool keys buffers by memory layout (size, align), so a buffer originally used for Vec<i32> can be reused for Vec<u32>, Vec<f32>, or any other 4-byte aligned type.

Usage

[dependencies]
vecpool = "0.1"
use vecpool::{get, with_capacity, PoolVec};

// Get a vec from the pool
let mut v = get::<u32>();
v.push(1);
v.push(2);
v.push(3);

// Use it exactly like a Vec<u32>
assert_eq!(v.len(), 3);
assert_eq!(&*v, &[1, 2, 3]);

// On drop, the buffer is returned to the pool
drop(v);

// Next request reuses the buffer — zero allocation cost
let v2 = get::<u32>();
assert!(v2.capacity() >= 3);

Features

  • Cross-type reuseVec<i32>Vec<u32>Vec<f32> share the same pool lane
  • Zero synchronization — one pool per thread, no mutexes or atomics
  • TransparentPoolVec<T> derefs to Vec<T>, all Vec methods work
  • Fast — power-of-two sizes use a direct array lookup (trailing_zeros), no hashing
  • Safe — all unsafe code is Miri-tested, no undefined behavior

API Overview

Function / Method Description
get::<T>() Get an empty pooled vec
with_capacity::<T>(n) Get a pooled vec with at least n capacity
PoolVec::new() Alias for get::<T>()
PoolVec::from(vec) Wrap an existing Vec<T> for pool return on drop
pool_vec.into_vec() Extract the inner Vec<T> (skips pool return)
.collect::<PoolVec<T>>() Collect an iterator into a pooled vec
pool_vec.into_iter() Iterate elements (buffer escapes the pool)
clear_pool() Free all pooled buffers on the current thread

How It Works

  1. Each thread has a local pool with "lanes" keyed by (size_of::<T>(), align_of::<T>())
  2. Each lane is a LIFO stack of raw buffers (pointer + byte capacity)
  3. get::<T>() pops a buffer from the matching lane and reconstructs a Vec<T> with len = 0
  4. On drop, PoolVec<T> clears elements, extracts the raw buffer, and pushes it back
  5. If no buffer is available, a fresh Vec is allocated normally

Fast Path

For types where size == align and size is a power of two ≤ 256 (covers all primitives, pointers, and most small types), the pool uses a fixed-size array indexed by size.trailing_zeros(). This avoids hashing entirely.

Thread Safety

PoolVec<T>: Send where T: Send. If a PoolVec is moved to another thread and dropped there, the buffer goes to that thread's pool. No synchronization is needed.

If the thread-local pool is being destroyed (thread exit), the buffer is freed normally instead of being pooled.

Benchmarks

Run benchmarks with:

cargo bench

The benchmark suite compares pooled vs unpooled allocation across different element sizes, pool depths, and usage patterns.

Minimum Supported Rust Version

Rust 2024 edition (1.85+).

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages