Thread Safe Object Pool
A high-performance, general-purpose object pool that reuses allocations instead of freeing them. Supports most standard library containers (Vec, HashMap, String, etc.) plus external types like IndexMap and triomphe::Arc.
Why Poolshark?
- Reduce allocations: Reuse containers instead of repeatedly allocating and freeing
- Predictable performance: Consistent behavior across platforms, independent of allocator quality
- Low-cost abstraction: Local pools have performance similar to thread_local! with simpler ergonomics
- Flexible: Choose between fast thread-local pools or lock-free cross-thread pools
- It's fast: View the benchmarks
Installation
Quick Start
use LPooled;
use HashMap;
// Take a HashMap from the thread-local pool (or create a new one if the pool is empty)
let mut map: = take;
map.insert;
// When dropped, the HashMap is cleared and returned to the pool
Which Pool Should I Use?
Use Local Pools (LPooled) when... |
Use Global Pools (GPooled) when... |
|---|---|
| Objects are created and dropped on the same thread(s) | One thread creates objects, other threads drop them |
| You want maximum performance | You need objects to return to a specific pool |
Rule of thumb: Start with LPooled (faster). Switch to GPooled
only if you have cross-thread producer-consumer patterns.
Local Pools
Local pools are thread-local but more ergonomic than
thread_local!. You can own the objects, pass them between threads,
and use them naturally. When dropped, objects return to the pool of
whichever thread drops them—not necessarily where they were created.
Thread safety: LPooled<T> is Send + Sync whenever T is Send + Sync, so you can safely pass pooled objects between threads.
Performance: Faster than global pools due to minimal atomic
operations, should not be significantly different than using
thread_local! directly. Use these by default unless you have a
cross-thread producer-consumer pattern.
Example: Deduplication With Minimal Allocations
use LPooled;
use ;
// dedup an unsorted vec. this will only allocate memory on,
// - the first call
// - deduping a vec that is bigger than any previously seen
// - deduping a vec that is bigger than the max length allowed in the pool
Global Pools
Global pools use lock-free queues to ensure objects always return to their origin pool, regardless of which thread drops them.
Thread safety: GPooled<T> is Send + Sync whenever T is Send + Sync, making it safe to share pooled objects across threads.
Performance: Will usually be faster than malloc/free. In cases where it isn't, it's usually close. Consistent across platforms with very different allocators.
Example: Producer-Consumer Pattern
use ;
use LazyLock;
use ;
// a batch is a vec of pooled strings
type Batch = ;
// strings will come from this pool. it can hold 1024 strings up to 4k in size.
// any string bigger than 4k will be thrown away. After the pool is full newly
// returned strings will be thrown away. This bounds the memory that can be
// consumed by this pool, but doesn't limit the number of strings that can exist.
static STRINGS: = new;
// batches will come from this pool, which can hold 1024 batches of up to 1024 elements
// in size.
static BATCHES: = new;
async
async
// Once an initial working set is allocated this program does not call
// malloc again, and free is never called except before exit.
// Depending on the platform allocator this is usually faster than a
// constant churn of malloc/free ops. Whether or not it's faster on a
// particular platform, it is more deterministic across platforms. Yes
// the platform allocator may pull all the tricks in the book and
// might even perform better, but move to some other platform and
// performance is awful again.
Supported Types
Built-in support (no additional code needed):
Vec<T>,VecDeque<T>,StringHashMap<K, V>,HashSet<K>IndexMap<K, V>,IndexSet<K>(withindexmapfeature)Option<T>whereTis poolable
Poolable Arc types:
Arc<T>- Drop-in replacement forstd::sync::Arcwith poolingTArc<T>- Lighter-weight Arc usingtriomphe::Arc(withtriomphefeature)
Custom types: Implement the Poolable trait (and optionally IsoPoolable for local pooling).
Features
triomphe(default): EnableTArc<T>poolable Arcindexmap(default): Enable pooling forIndexMapandIndexSetserde(default): Serialize/deserialize support for pooled types