3 releases (breaking)
0.3.0 | Feb 16, 2021 |
---|---|
0.2.0 | Aug 17, 2019 |
0.1.0 | Aug 17, 2019 |
#1817 in Data structures
23KB
434 lines
swimmer
Thread-safe object pools for Rust.
use swimmer::Pool;
let pool: Pool<String> = Pool::with_size(10);
assert_eq!(pool.size(), 10);
let value = pool.get()
assert_eq!(pool.size(), 9);
assert_eq!(*value, "");
drop(value);
// Value is returned to pool
assert_eq!(pool.size(), 10);
See the documentation for more.
lib.rs
:
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();
Dependencies
~43–280KB