#object-pool #lock-free #non-blocking #atomic

lockfree-object-pool

A thread-safe object pool collection with automatic return and attach/detach semantics

6 releases

0.1.5 Jan 24, 2024
0.1.4 Apr 10, 2023
0.1.3 Jan 25, 2021

#46 in Concurrency

Download history 5538/week @ 2024-01-02 5611/week @ 2024-01-09 8224/week @ 2024-01-16 7646/week @ 2024-01-23 8000/week @ 2024-01-30 6864/week @ 2024-02-06 7610/week @ 2024-02-13 7721/week @ 2024-02-20 8505/week @ 2024-02-27 6055/week @ 2024-03-05 6356/week @ 2024-03-12 7978/week @ 2024-03-19 8368/week @ 2024-03-26 8831/week @ 2024-04-02 7516/week @ 2024-04-09 6301/week @ 2024-04-16

32,119 downloads per month
Used in 6 crates (2 directly)

BSL-1.0 license

2.5MB
754 lines

Lock Free Object Pool

License Cargo Documentation CI

A thread-safe object pool collection with automatic return.

Some implementations are lockfree :

  • LinearObjectPool
  • SpinLockObjectPool

Other use std::Mutex :

  • MutexObjectPool

And NoneObjectPool basic allocation without pool.

Usage

[dependencies]
lockfree-object-pool = "0.1"
extern crate lockfree_object_pool;

Example

The general pool creation looks like this for

 let pool = LinearObjectPool::<u32>::new(
     ||  Default::default(), 
     |v| {*v = 0; });

And use the object pool

  let mut item = pool.pull();
  *item = 5;
  ...  

At the end of the scope item return in object pool.

Interface

All implementations support same interface :

struct ObjectPool<T> {  
}

impl<T> ObjectPool<T> {
  // for LinearObjectPool, SpinLockObjectPool and MutexObjectPool
  // init closure used to create an element
  // reset closure used to reset element a dropped element
  pub fn new<R, I>(init: I, reset: R) -> Self
    where
        R: Fn(&mut T) + 'static + Send + Sync,
        I: Fn() -> T + 'static + Send + Sync + Clone,
    {
      ...
    }

  // for NoneObjectPool
  // init closure used to create an element
  pub fn new<I>(init: I) -> Self
    where
        I: Fn() -> T + 'static
    {
      ...
    }

  pub fn pull(&self) -> Reusable<T> {
    ...
  }

  pub fn pull_owned(self: &Arc<Self>) -> OwnedReusable<T> {
    ...
  }
}

struct Reusable<T> {  
}

impl<'a, T> DerefMut for Reusable<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        ...
    }
}

impl<'a, T> Deref for Reusable<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        ...
    }
}

struct OwnedReusable<T> {  
}

impl<'a, T> DerefMut for OwnedReusable<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        ...
    }
}

impl<'a, T> Deref for OwnedReusable<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        ...
    }
}

Multithreading

All implementation support allocation/desallocation from on or more thread. You only need to wrap the pool in a std::sync::Arc :

 let pool = Arc::new(LinearObjectPool::<u32>::new(
     ||  Default::default(), 
     |v| {*v = 0; }));

Performance

Global report.

Allocation

ObjectPool Duration in Monothreading (us) Duration Multithreading (us)
NoneObjectPool 1.2848 0.62509
MutexObjectPool 1.3107 1.5178
SpinLockObjectPool 1.3106 1.3684
LinearObjectPool 0.23732 0.38913
crate 'sharded-slab' 1.6264 0.82607
crate 'object-pool' 0.77533 0.26224

Report monothreading and multithreading

Forward Message between Thread

ObjectPool 1 Reader - 1 Writter (ns) 5 Reader - 1 Writter (ns) 1 Reader - 5 Writter (ns) 5 Reader - 5 Writter (ns)
NoneObjectPool 529.75 290.47 926.05 722.35
MutexObjectPool 429.29 207.17 909.88 409.99
SpinLockObjectPool 34.277 182.62 1089.7 483.81
LinearObjectPool 43.876 163.18 365.56 326.92
crate 'sharded-slab' 525.82 775.79 966.87 1289.2

Not supported by crate 'object-pool'

Report 1-1, 5-1, 1-5 , 5-5

Desallocation

ObjectPool Duration in Monothreading (ns) Duration Multithreading (ns)
NoneObjectPool 111.81 93.585
MutexObjectPool 26.108 101.86
SpinLockObjectPool 22.441 50.107
LinearObjectPool 7.5379 41.707
crate 'sharded-slab' 7.0394 10.283
crate 'object-pool' 20.517 44.798

Report monothreading and multithreading

Comparison with Similar Crates

  • crate 'sharded-slab': I like pull interface but i dislike

    • Default / Reset trait because not enough flexible
    • Performance
    • create_owned method not use a reference on Self
  • crate 'object-pool': use a spinlock to sync and the performance are pretty good but i dislike :

    • need to specify fallback at each pull call :
    use object_pool::Pool;
    let pool = Pool::<Vec<u8>>::new(32, || Vec::with_capacity(4096);
    // ...
    let item1 = pool.pull(|| Vec::with_capacity(4096));
    // ...
    let item2 = pool.pull(|| Vec::with_capacity(4096));
    
    • no reset mechanism, need to do manually
    • no possiblity to forward data between thread

TODO

  • why the object-pool with spinlock has so bad performance compared to spinlock mutex use by crate 'object-pool'
  • impl a tree object pool (cf toolsbox)

Implementation detail

TODO

Licence

cf Boost Licence

No runtime deps