flaque
A fast, concurrent, lock-free, and sometimes wait-free, pool implementation.
Quick overview
A pool is a collection of objects of limited size. When an object is fetched from the pool, the number of available objects decreases by one. Once the fetched object is dropped, it goes back into the pool, and the number of available objects is increased by one.
All objects are lazily created with a builder. Objects are recycled, i.e. when an object has been built and fetched once, it can be mutated, and when it goes back in the pool, it stays as is, untouched.
use Pool;
// Let's define what the pool will hold.
// Let's create the pool: capacity of 8, and the builder for the `T`s.
let pool = new;
// Let's try to get an object from the pool.
let object_0 = pool.try_get.unwrap;
// Or, let's wait to get an object from the pool.
let object_1 = pool.get.await;
The main methods are:
Pool::try_getif one wants to synchronously, atomically get an object from the pool,Pool::getif one wants to asynchronously get an object from the pool.
An object is represented by the Object<T> type, which derefs and
deref_muts to T. When it drops, the value returns immediately to the pool.
Features
Lock- and wait-free
This pool implementation is lock-free and sometimes wait-free. It is designed to be fast and consistent.
| Operation | Lock-free | Wait-free |
|---|---|---|
Pool::try_get |
yes | yes |
Pool::get |
yes | no |
Object::drop |
yes | yes |
Pool::close |
yes | no |
Pool::reopen |
yes | yes |
If you're not familiar with these definitions, please read Non-blocking algorithm.
Get and Close futures
Pool::get returns a Get future. It resolves to Some(object) once one is
available in the pool, otherwise it returns to None if the pool is closed.
Pool::close returns a Close future. It resolves once all Objects are back
into the pool, i.e. when they are all dropped. Pool::close takes self, i.e.
it takes ownership of the pool. It will be returned back once the future
resolves. This guarantees that it is impossible to reopen the pool, and to fetch
new Objects, while waiting for all Objects to return back into the pool.
Thus, this is not unusual to see:
let pool = pool.close.await;
Testing
This crate is tested with loom and
miri.
Benchmarks
This crate has started as a hobby project, but also with the idea of replacing
deadpool inside the Matrix Rust SDK. Indeed, we are
experiencing performance issues due to deadpool. Here are the results of the
benchmarks comparing flaque with deadpool.
Each benchmark basically runs the following code: pool.get().await; —i.e. get
and release many objects— multiple times, on different threads,
The first group (pool size of 16, and 8 readers) represents the no contention scenario: there are ½ readers per object. The second group (pool size of 16, 2048 readers) represents the high contention on a small pool scenario: there are 128 readers per object. The third group (pool size of 512, 1 million readers) represents the high contention on a large pool scenario: there are 2048 readers per object.
On average, flaque is 1.7 times faster than deadpool.
The time is in milliseconds. The throughput is in millions of objects per second.