nexus-pool
High-performance object pools for latency-sensitive applications.
Features
- Sub-100 cycle operations: ~26 cycles for local pools, ~42-68 cycles for sync pools
- Zero allocation on hot path: Pre-allocate objects at startup
- RAII guards: Objects automatically return to pool on drop
- Manual take/put: Owned values without RAII when guard lifetime doesn't fit
- Graceful shutdown: Guards safely drop values if pool is gone
Quick Start
use BoundedPool;
// Create a pool of 100 pre-allocated buffers
let pool = new;
// Acquire and use
let mut buf = pool.try_acquire.expect;
buf.extend_from_slice;
// Automatically returns to pool when `buf` drops
Pool Types
local::BoundedPool / local::Pool
Single-threaded pools with zero synchronization overhead.
use Pool;
// Growable pool - creates objects on demand
let pool = new;
// RAII: auto-returns to pool on drop
let buf = pool.acquire;
// Manual: caller controls lifetime
let mut buf = pool.take;
buf.extend_from_slice;
pool.put; // reset is called, value returns to pool
sync::Pool
Thread-safe pool: one thread acquires, any thread can return.
use Pool;
let pool = new;
let buf = pool.try_acquire.unwrap;
// Send to another thread - returns to pool when dropped
spawn;
Design Philosophy
Predictability over generality.
This crate intentionally does not provide MPMC (multi-producer multi-consumer) pools. Here's why:
-
MPMC requires solving ABA: Generation counters, hazard pointers, or epoch-based reclamation add overhead and complexity.
-
MPMC is a design smell: If multiple threads contend for the same pool, you've created a bottleneck. The pool that was supposed to reduce latency now adds it.
-
Better alternatives exist:
- Per-thread pools (
local::Poolper thread) - Sharded pools (hash thread ID to pool index)
- Message passing (send buffers through channels)
- Per-thread pools (
If you truly need MPMC, use crossbeam::ArrayQueue.
Performance
Measured on Intel Core i9 @ 3.1 GHz:
| Pool | Acquire p50 | Release p50 | Release p99 |
|---|---|---|---|
local::BoundedPool |
26 cycles | 26 cycles | 58 cycles |
local::Pool (reuse) |
26 cycles | 26 cycles | 58 cycles |
local::Pool (factory) |
32 cycles | 26 cycles | 58 cycles |
sync::Pool (same thread) |
42 cycles | 68 cycles | 74 cycles |
sync::Pool (cross-thread) |
42 cycles | 68 cycles | 86 cycles |
Run the benchmarks yourself:
Use Cases
Trading Systems
use Pool;
// Order entry thread owns the pool
let pool = new;
// Hot path: acquire order, fill, send to matching engine
let mut order = pool.try_acquire.expect;
order.symbol = symbol;
order.price = price;
order.quantity = qty;
// Send to matching engine thread
matching_engine_tx.send.unwrap;
// Order returns to pool when matching engine drops it
Network Buffers
use BoundedPool;
// Per-connection buffer pool
let buffers = new;
loop
Implementation Notes
Reset closure panics: If the reset closure passed to the pool constructor panics during put() or guard drop, the object is lost (not returned to the pool). The panic propagates normally. Design reset closures to be infallible.
sync::Pool internals: sync::Pool uses AtomicUsize for the free-list head pointer, enabling lock-free return from any thread. Acquire is single-threaded only (&mut self).
Minimum Supported Rust Version
Rust 1.85 or later.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.