Crate swimmer[−][src]
A thread-safe object pool for Rust.
An object pool is used to reuse objects without reallocating them. When an object is requested from a pool, it is taken out of the pool; once it is dropped, it is returned to the pool and can be retrieved once more.
The main type of this crate is the Pool
struct, which implements a thread-safe object pool.
It can pool objects which implement Recyclable
,
a trait which allows the pool to initialize and "recycle"
an object.
The implementation of this is as follows:
- A pool is created using the
builder
function. It is configured with an initial size. - Upon creation of the pool, the pool initializes
initial_size
values usingRecyclable
'snew
function. - When a value is requested from the pool, usually
using
Pool::get()
, a value is taken out of the internal buffer. If there are no remaining values, a new object is initialized usingRecyclable::new()
. - The value can then be used by the caller.
- When the value is dropped, it is returned to the pool,
and future calls to
Pool::get()
may return the same object.
To ensure that the object is cleaned, the pool calls Recyclable::recycle()
on the object before returning it to the pool. This function removes
any mutated state of the object, effectively "resetting" it. For
example, see the following sequence of events:
- A pool of vectors is initialized.
- A vector is retrieved from the pool, and some values are added to it.
- The vector is dropped and returned to the pool.
Without resetting the vector, future calls to Pool::get
could return
a vector containing those old elements; clearly, this is not desirable.
As a result, the Recyclable
implementation for Vec
clears the
vector when recycling.
This crate is heavily based on the lifeguard
crate, but
it is thread-safe, while lifeguard
is not.
Thread safety
Pool
is thread-safe, and it can be shared across threads
or used in a lazily-initialized static variable (see the examples).
This is currently implemented by making the pool contain
a thread-local buffer for each thread, which has been proving
by benchmarks to be more than twice as performant as using
a locked Vec
or crossbeam::SegQueue
.
Supplier
In some cases, you may want to specify your own function
for initializing new objects rather than use the default
Recyclable::new()
function. In this case, you can optionally
use PoolBuilder::with_supplier()
, which will cause
the pool to use the provided closure to initialize
new values.
For example, the Recyclable
implementation for Vec<T>
allocates a vector with zero capacity, but you may want
to give the vector an initial capacity. In that case,
you can do this, for example:
use swimmer::Pool; let pool: Pool<Vec<u32>> = swimmer::builder() .with_supplier(|| Vec::with_capacity(128)) .build(); let vec = pool.get(); assert_eq!(vec.capacity(), 128);
Note, however, that the supplier function is only called when the object is first initialized: it is not used to recycle the object. This means that there is currently no way to implement custom recycling functionality.
Crate features
hashbrown-impls
: implementsRecyclable
forhashbrown::HashMap
andhashbrown::HashSet
.smallvec-impls
: implementsRecyclable
forSmallVec
.
Examples
Basic usage:
use swimmer::Pool; // Initialize a new pool, allocating // 10 empty values to start let pool: Pool<String> = swimmer::builder() .with_starting_size(10) .build(); assert_eq!(pool.size(), 10); let mut string = pool.get(); assert_eq!(*string, ""); // Note that you need to dereference the string, since it is stored in a special smart pointer string.push_str("test"); // Mutate the string // One object was taken from the pool, // so its size is now 9 assert_eq!(pool.size(), 9); // Now, the string is returned to the pool drop(string); assert_eq!(pool.size(), 10); // Get another string from the pool. This string // could be the same one retrieved above, but // since the string is cleared before returning // into the pool, it is now empty. However, it // retains any capacity which was allocated, // which prevents additional allocations // from occurring. let another_string = pool.get(); assert_eq!(*another_string, "");
Implementing Recyclable
on your own object:
use swimmer::{Pool, Recyclable}; struct Person { name: String, age: u32, } impl Recyclable for Person { fn new() -> Self { Self { name: String::new(), age: 0, } } fn recycle(&mut self) { // You are responsible for ensuring // that modified `Person`s get reset // before being returned to the pool. // Otherwise, the object could be put // back into the pool with its old state // still intact; this could cause weird behavior! self.name.clear(); self.age = 0; } } let pool: Pool<Person> = Pool::new(); let mut josh = pool.get(); josh.name.push_str("Josh"); // Since `recycle` empties the string, this will effectively set `name` to `Josh` josh.age = 47; drop(josh); // Josh is returned to the pool and his name and age are reset // Now get a new person let another_person = pool.get();
Using a Pool
object in a lazy_static
variable,
allowing it to be used globally:
use lazy_static::lazy_static; use swimmer::Pool; lazy_static! { static ref POOL: Pool<String> = { Pool::new() }; } let value = POOL.get();
Structs
Pool | A thread-safe object pool, used to reuse objects without reallocating. |
PoolBuilder | A pool builder, used to configure various pool settings. |
Recycled | A smart pointer which returns the contained object to its pool once dropped. |
Traits
Recyclable | Indicates that an object can be used
inside a |
Functions
builder | Creates a new |
Type Definitions
Supplier | A supplier function, used to initialize new objects for a pool. |