lockfree-object-pool-0.1.6/.cargo_vcs_info.json0000644000000001360000000000100150450ustar { "git": { "sha1": "67952a47b1a73b54aa2bcebfa946df204ec68a16" }, "path_in_vcs": "" }lockfree-object-pool-0.1.6/.gitattributes000064400000000000000000000000471046102023000165310ustar 00000000000000benches/criterion/** linguist-vendored lockfree-object-pool-0.1.6/.github/workflows/ci.yml000064400000000000000000000034171046102023000203550ustar 00000000000000name: CI on: pull_request: push: branches: - master schedule: - cron: "0 2 * * 1" env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 jobs: check: name: Check runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - name: Install dependencies run: | sudo apt-get update -y -qq - name: Run cargo check run: cargo check test: name: Test Suite runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - name: Install dependencies run: | sudo apt-get update -y -qq - name: Run cargo test run: cargo test --verbose clippy: name: Lint with clippy runs-on: ubuntu-latest env: RUSTFLAGS: -Dwarnings steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - name: Install dependencies run: | sudo apt-get update -y -qq - name: Run cargo clippy run: cargo clippy --workspace --all-targets --verbose -- -A dead-code rustfmt: name: Verify code formatting runs-on: ubuntu-latest env: RUSTFLAGS: -Dwarnings steps: - name: Checkout sources uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - name: Install dependencies run: | sudo apt-get update -y -qq - name: Run cargo clippy run: cargo fmt --all -- --check lockfree-object-pool-0.1.6/.gitignore000064400000000000000000000000231046102023000156200ustar 00000000000000/target Cargo.lock lockfree-object-pool-0.1.6/.vscode/settings.json000064400000000000000000000003041046102023000177260ustar 00000000000000{ "rust-analyzer.check.command": "clippy", "rust-analyzer.checkOnSave": true, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true } } lockfree-object-pool-0.1.6/Cargo.toml0000644000000025510000000000100130460ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "lockfree-object-pool" version = "0.1.6" authors = ["Etienne Vaillant "] exclude = ["benches/criterion"] description = "A thread-safe object pool collection with automatic return and attach/detach semantics." homepage = "https://github.com/EVaillant/lockfree-object-pool" documentation = "https://docs.rs/lockfree-object-pool" readme = "README.md" keywords = [ "atomic", "object-pool", "non-blocking", "lock-free", ] categories = [ "concurrency", "memory-management", "data-structures", ] license = "BSL-1.0" repository = "https://github.com/EVaillant/lockfree-object-pool" [[bench]] name = "bench" harness = false [dependencies] [dev-dependencies.criterion] version = "0.5.1" [dev-dependencies.criterion-plot] version = "0.5" [dev-dependencies.object-pool] version = "0.5" [dev-dependencies.sharded-slab] version = "0.1" lockfree-object-pool-0.1.6/Cargo.toml.orig000064400000000000000000000014301046102023000165220ustar 00000000000000[package] name = "lockfree-object-pool" description = "A thread-safe object pool collection with automatic return and attach/detach semantics." version = "0.1.6" readme = "README.md" authors = ["Etienne Vaillant "] edition = "2021" license = "BSL-1.0" documentation = "https://docs.rs/lockfree-object-pool" homepage = "https://github.com/EVaillant/lockfree-object-pool" repository = "https://github.com/EVaillant/lockfree-object-pool" keywords = ["atomic", "object-pool", "non-blocking", "lock-free"] categories = ["concurrency", "memory-management", "data-structures"] exclude = ["benches/criterion"] [dependencies] [dev-dependencies] sharded-slab = "0.1" object-pool = "0.5" criterion = "0.5.1" criterion-plot = "0.5" [[bench]] name = "bench" harness = false lockfree-object-pool-0.1.6/LICENSE_1_0.txt000064400000000000000000000024711046102023000161230ustar 00000000000000Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.lockfree-object-pool-0.1.6/README.md000064400000000000000000000147201046102023000151200ustar 00000000000000# Lock Free Object Pool [![License](https://img.shields.io/badge/License-Boost%201.0-lightblue.svg)](https://github.com/EVaillant/lockfree-object-pool) [![Cargo](https://img.shields.io/crates/v/lockfree-object-pool.svg)](https://crates.io/crates/lockfree-object-pool) [![Documentation](https://docs.rs/lockfree-object-pool/badge.svg)]( https://docs.rs/lockfree-object-pool) ![CI](https://github.com/EVaillant/lockfree-object-pool/workflows/CI/badge.svg) 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 ```toml [dependencies] lockfree-object-pool = "0.1" ``` ```rust extern crate lockfree_object_pool; ``` ### Example The general pool creation looks like this for ```rust let pool = LinearObjectPool::::new( || Default::default(), |v| {*v = 0; }); ``` And use the object pool ```rust let mut item = pool.pull(); *item = 5; ... ``` At the end of the scope item return in object pool. ### Interface All implementations support same interface : ```rust struct ObjectPool { } impl ObjectPool { // for LinearObjectPool, SpinLockObjectPool and MutexObjectPool // init closure used to create an element // reset closure used to reset element a dropped element pub fn new(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(init: I) -> Self where I: Fn() -> T + 'static { ... } pub fn pull(&self) -> Reusable { ... } pub fn pull_owned(self: &Arc) -> OwnedReusable { ... } } struct Reusable { } 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 { } 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`] : ```rust let pool = Arc::new(LinearObjectPool::::new( || Default::default(), |v| {*v = 0; })); ``` ### Performance Global [report](https://evaillant.github.io/lockfree-object-pool/benches/criterion/report/index.html). #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion/allocation/report/index.html) and [multithreading](https://evaillant.github.io/lockfree-object-pool/benches/criterion/multi%20thread%20allocation/report/index.html) #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_1%20nb_readder_1)/report/index.html), [5-1](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_1%20nb_readder_5)/report/index.html), [1-5](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_5%20nb_readder_1)/report/index.html) , [5-5](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_5%20nb_readder_5)/report/index.html) #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion/free/report/index.html) and [multithreading](https://evaillant.github.io/lockfree-object-pool/benches/criterion/multi%20thread%20free/report/index.html) ### 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 : ```rust use object_pool::Pool; let pool = Pool::>::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](http://www.boost.org/LICENSE_1_0.txt) ### Related Projects - [`crate 'object-pool'`] - A thread-safe object pool in rust with mutex - [`crate 'sharded-slab'`] - A lock-free concurrent slab - [`toolsbox`] - Some object pool implementation en c++ [`crate 'sharded-slab'`]: https://crates.io/crates/sharded-slab [`crate 'object-pool'`]: https://crates.io/crates/object-pool [`toolsbox`]: https://github.com/EVaillant/toolsbox lockfree-object-pool-0.1.6/benches/bench.rs000064400000000000000000000257021046102023000166770ustar 00000000000000#[macro_use] extern crate criterion; use criterion::Criterion; mod bench_tools; #[macro_use] mod bench_generic; struct Vec4096 { _data: Vec, } impl Default for Vec4096 { fn default() -> Self { Self { _data: Vec::with_capacity(16 * 1024), } } } impl sharded_slab::Clear for Vec4096 { fn clear(&mut self) {} } fn bench_alloc(c: &mut Criterion) { let mut group = c.benchmark_group("allocation"); bench_alloc_impl_!( group, "none object poll", lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)), 1 ); bench_alloc_impl_!( group, "mutex object poll", lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_impl_!( group, "spin_lock object poll", lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_impl_!( group, "linear object poll", lockfree_object_pool::LinearObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_impl_!( group, "crate 'object-pool'", object_pool::Pool::>::new(32, || Vec::with_capacity(4096)), 2 ); bench_alloc_impl_!( group, "crate 'sharded-slab'", sharded_slab::Pool::::new(), 3 ); group.finish(); } fn bench_free(c: &mut Criterion) { let mut group = c.benchmark_group("free"); bench_free_impl_!( group, "none object poll", lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)), 1 ); bench_free_impl_!( group, "mutex object poll", lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_impl_!( group, "spin_lock object poll", lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_impl_!( group, "linear object poll", lockfree_object_pool::LinearObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_impl_!( group, "crate 'object-pool'", object_pool::Pool::>::new(32, || Vec::with_capacity(4096)), 2 ); bench_free_impl_!( group, "crate 'sharded-slab'", sharded_slab::Pool::::new(), 3 ); group.finish(); } fn bench_reuse(c: &mut Criterion) { const VEC_SIZE: usize = 16384; const BATCH_SIZE: usize = 8192; let mut group = c.benchmark_group("reuse"); group.bench_function("none object poll", |b| { b.iter_batched( || { ( lockfree_object_pool::NoneObjectPool::new(|| { Vec::::with_capacity(16 * 1024) }), Vec::with_capacity(VEC_SIZE), ) }, |(pool, mut vec)| { for index in 0..BATCH_SIZE { vec.insert(index, criterion::black_box(pool.pull())); } }, criterion::BatchSize::SmallInput, ); }); group.bench_function("mutex object poll", |b| { b.iter_batched( || { let pool = lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {}, ); let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect(); drop(v); (pool, Vec::with_capacity(VEC_SIZE)) }, |(pool, mut vec)| { for index in 0..BATCH_SIZE { vec.insert(index, criterion::black_box(pool.pull())); } }, criterion::BatchSize::SmallInput, ); }); group.bench_function("spin_lock object poll", |b| { b.iter_batched( || { let pool = lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {}, ); let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect(); drop(v); (pool, Vec::with_capacity(VEC_SIZE)) }, |(pool, mut vec)| { for index in 0..BATCH_SIZE { vec.insert(index, criterion::black_box(pool.pull())); } }, criterion::BatchSize::SmallInput, ); }); group.bench_function("linear object poll", |b| { b.iter_batched( || { let pool = lockfree_object_pool::LinearObjectPool::new( || Vec::::with_capacity(16 * 1024), |_v| {}, ); let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.pull()).collect(); drop(v); (pool, Vec::with_capacity(VEC_SIZE)) }, |(pool, mut vec)| { for index in 0..BATCH_SIZE { vec.insert(index, criterion::black_box(pool.pull())); } }, criterion::BatchSize::SmallInput, ); }); group.bench_function("crate 'object-pool'", |b| { b.iter_batched( || { let pool = object_pool::Pool::new(VEC_SIZE, || Vec::::with_capacity(16 * 1024)); let v: Vec<_> = (0..VEC_SIZE).map(|_| pool.try_pull().unwrap()).collect(); drop(v); (pool, Vec::with_capacity(VEC_SIZE)) }, |(pool, mut vec)| { for index in 0..BATCH_SIZE { vec.insert(index, criterion::black_box(pool.try_pull().unwrap())); } }, criterion::BatchSize::SmallInput, ); }); } fn bench_alloc_mt(c: &mut Criterion) { let mut group = c.benchmark_group("multi thread allocation"); bench_alloc_mt_impl_!( group, "none object poll", lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)), 1 ); bench_alloc_mt_impl_!( group, "mutex object poll", lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_mt_impl_!( group, "spin_lock object poll", lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_mt_impl_!( group, "linear object poll", lockfree_object_pool::LinearObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_alloc_mt_impl_!( group, "crate 'object-pool'", object_pool::Pool::>::new(32, || Vec::with_capacity(4096)), 2 ); bench_alloc_mt_impl_!( group, "crate 'sharded-slab'", sharded_slab::Pool::::new(), 3 ); group.finish(); } fn bench_free_mt(c: &mut Criterion) { let mut group = c.benchmark_group("multi thread free"); bench_free_mt_impl_!( group, "none object poll", lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)), 1 ); bench_free_mt_impl_!( group, "mutex object poll", lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_mt_impl_!( group, "spin_lock object poll", lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_mt_impl_!( group, "linear object poll", lockfree_object_pool::LinearObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), 1 ); bench_free_mt_impl_!( group, "crate 'object-pool'", object_pool::Pool::>::new(32, || Vec::with_capacity(4096)), 2 ); bench_free_mt_impl_!( group, "crate 'sharded-slab'", sharded_slab::Pool::::new(), 3 ); group.finish(); } fn bench_forward_multi_thread(c: &mut Criterion, nb_writter: usize, nb_readder: usize) { let mut group = c.benchmark_group(format!( "forward msg from pull (nb_writter:{} nb_readder:{})", nb_writter, nb_readder )); bench_forward_impl_!( group, "none object poll", lockfree_object_pool::NoneObjectPool::new(|| Vec::::with_capacity(16 * 1024)), nb_readder, nb_writter, 1 ); bench_forward_impl_!( group, "mutex object poll", lockfree_object_pool::MutexObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), nb_readder, nb_writter, 1 ); bench_forward_impl_!( group, "spin_lock object poll", lockfree_object_pool::SpinLockObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), nb_readder, nb_writter, 1 ); bench_forward_impl_!( group, "linear object poll", lockfree_object_pool::LinearObjectPool::>::new( || Vec::with_capacity(16 * 1024), |_v| {} ), nb_readder, nb_writter, 1 ); bench_forward_impl_!( group, "crate 'sharded-slab'", sharded_slab::Pool::::new(), nb_readder, nb_writter, 3 ); group.finish(); } fn bench_forward_multi_thread55(c: &mut Criterion) { bench_forward_multi_thread(c, 5, 5); } fn bench_forward_multi_thread15(c: &mut Criterion) { bench_forward_multi_thread(c, 1, 5); } fn bench_forward_multi_thread51(c: &mut Criterion) { bench_forward_multi_thread(c, 5, 1); } fn bench_forward_multi_thread11(c: &mut Criterion) { bench_forward_multi_thread(c, 1, 1); } criterion_group!( forward_multi_thread, bench_forward_multi_thread55, bench_forward_multi_thread15, bench_forward_multi_thread51, bench_forward_multi_thread11 ); criterion_group!(multi_thread, bench_alloc_mt, bench_free_mt); criterion_group!(mono_thread, bench_alloc, bench_free, bench_reuse); criterion_main!(mono_thread, multi_thread, forward_multi_thread); lockfree-object-pool-0.1.6/benches/bench_generic.rs000064400000000000000000000157721046102023000204010ustar 00000000000000#[macro_export] macro_rules! pull_ { ($pool:ident, 1) => { $pool.pull() }; ($pool:ident, 2) => { $pool.pull(|| Vec::with_capacity(4096)) }; ($pool:ident, 3) => { $pool.create().unwrap() }; } #[macro_export] macro_rules! pull_forward_ { ($pool:ident, 1) => { $pool.pull_owned() }; ($pool:ident, 3) => { $pool.clone().create_owned().unwrap() }; } #[macro_export] macro_rules! bench_alloc_impl_ { ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => { $group.bench_function($name, |b| { let pool = $expression; let mut items = Vec::new(); b.iter(|| { items.push(pull_!(pool, $pull_impl)); }); }); }; } #[macro_export] macro_rules! bench_free_impl_ { ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => { $group.bench_function($name, |b| { b.iter_custom(|iter| { use std::time::Instant; let pool = $expression; let mut items = Vec::new(); for _ in 0..iter { items.push(pull_!(pool, $pull_impl)); } let start = Instant::now(); items.clear(); start.elapsed() }); }); }; } #[macro_export] macro_rules! bench_alloc_mt_impl_ { ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => { $group.bench_function($name, |b| { b.iter_custom(|iter| { use std::sync::Arc; use std::sync::Barrier; use std::thread; use std::time::Instant; let pool = Arc::new($expression); let start_barrier = Arc::new(Barrier::new(6)); let stop_barrier = Arc::new(Barrier::new(6)); let mut children = Vec::new(); for _ in 0..5 { let pool = Arc::clone(&pool); let start_barrier = Arc::clone(&start_barrier); let stop_barrier = Arc::clone(&stop_barrier); let child = thread::spawn(move || { let mut items = Vec::with_capacity(iter as usize); start_barrier.wait(); for _ in 0..iter { items.push(pull_!(pool, $pull_impl)); } stop_barrier.wait(); }); children.push(child); } start_barrier.wait(); let start = Instant::now(); stop_barrier.wait(); let duration = start.elapsed() / 5; for child in children { child.join().unwrap(); } duration }); }); }; } #[macro_export] macro_rules! bench_free_mt_impl_ { ($group:expr, $name:literal, $expression:expr, $pull_impl:tt) => { $group.bench_function($name, |b| { b.iter_custom(|iter| { use std::sync::Arc; use std::sync::Barrier; use std::thread; use std::time::Instant; let pool = Arc::new($expression); let start_barrier = Arc::new(Barrier::new(6)); let stop_barrier = Arc::new(Barrier::new(6)); let mut children = Vec::new(); for _ in 0..5 { let pool = Arc::clone(&pool); let start_barrier = Arc::clone(&start_barrier); let stop_barrier = Arc::clone(&stop_barrier); let child = thread::spawn(move || { let mut items = Vec::with_capacity(iter as usize); for _ in 0..iter { items.push(pull_!(pool, $pull_impl)); } start_barrier.wait(); items.clear(); stop_barrier.wait(); }); children.push(child); } start_barrier.wait(); let start = Instant::now(); stop_barrier.wait(); let duration = start.elapsed() / 5; for child in children { child.join().unwrap(); } duration }); }); }; } #[macro_export] macro_rules! bench_forward_impl_ { ($group:expr, $name:literal, $expression:expr, $nb_readder:ident, $nb_writter:ident, $pull_impl:tt) => { $group.bench_function($name, |b| { b.iter_custom(|iter| { use bench_tools::Queue; use std::sync::Arc; use std::sync::Barrier; use std::thread; use std::time::Instant; let pool = Arc::new($expression); let queue = Arc::new(Queue::new()); let start_barrier = Arc::new(Barrier::new($nb_readder + $nb_writter + 1)); let stop_reader_barrier = Arc::new(Barrier::new($nb_readder + 1)); let stop_writer_barrier = Arc::new(Barrier::new($nb_writter + 1)); let mut children = Vec::new(); for _ in 0..$nb_readder { let queue = Arc::clone(&queue); let start_barrier = Arc::clone(&start_barrier); let stop_reader_barrier = Arc::clone(&stop_reader_barrier); let child = thread::spawn(move || { start_barrier.wait(); loop { let elt = queue.pop(); if elt.is_none() { break; } } stop_reader_barrier.wait(); }); children.push(child); } for _ in 0..$nb_writter { let pool = Arc::clone(&pool); let queue = Arc::clone(&queue); let start_barrier = Arc::clone(&start_barrier); let stop_writer_barrier = Arc::clone(&stop_writer_barrier); let child = thread::spawn(move || { start_barrier.wait(); for _ in 0..iter { queue.push(pull_forward_!(pool, $pull_impl)) } stop_writer_barrier.wait(); }); children.push(child); } start_barrier.wait(); let start = Instant::now(); stop_writer_barrier.wait(); queue.stop(); stop_reader_barrier.wait(); let duration = start.elapsed() / 5; for child in children { child.join().unwrap(); } duration }); }); }; } lockfree-object-pool-0.1.6/benches/bench_tools.rs000064400000000000000000000025351046102023000201160ustar 00000000000000use std::collections::VecDeque; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Condvar, Mutex}; pub struct Queue { data: Mutex>, condvar: Condvar, stop: AtomicBool, } impl Queue { pub fn new() -> Self { Self { data: Mutex::new(VecDeque::new()), condvar: Condvar::new(), stop: AtomicBool::new(false), } } pub fn push(&self, value: T) { let mut datas = self.data.lock().unwrap(); let empty = datas.is_empty(); datas.push_back(value); if empty { self.condvar.notify_all(); } } pub fn stop(&self) { let _datas = self.data.lock().unwrap(); self.stop.store(true, Ordering::Relaxed); self.condvar.notify_all(); } pub fn pop(&self) -> Option { let mut datas = self.data.lock().unwrap(); if !datas.is_empty() { datas.pop_front() } else if self.stop.load(Ordering::Relaxed) { None } else { self.condvar .wait_while(datas, |datas| { datas.is_empty() && !self.stop.load(Ordering::Relaxed) }) .unwrap() .pop_front() } } } impl Default for Queue { fn default() -> Self { Self::new() } } lockfree-object-pool-0.1.6/src/lib.rs000064400000000000000000000111061046102023000155370ustar 00000000000000//! 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. //! //! ## Example //! //! The general pool creation looks like this for //! ```rust //! use lockfree_object_pool::LinearObjectPool; //! //! let pool = LinearObjectPool::::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. //! ## Multithreading //! //! All implementation support allocation/desallocation from on or more thread. You only need to wrap the pool in a [`std::sync::Arc`] : //! //! ```rust //! use lockfree_object_pool::LinearObjectPool; //! use std::sync::Arc; //! //! let pool = Arc::new(LinearObjectPool::::new( //! || Default::default(), //! |v| {*v = 0; })); //! ``` //! ## Performance //! //! Global [report](https://evaillant.github.io/lockfree-object-pool/benches/criterion/report/index.html). //! //! //! #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion/allocation/report/index.html) and [multithreading](https://evaillant.github.io/lockfree-object-pool/benches/criterion/multi%20thread%20allocation/report/index.html) //! //! #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_1%20nb_readder_1)/report/index.html), [5-1](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_1%20nb_readder_5)/report/index.html), [1-5](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_5%20nb_readder_1)/report/index.html) , [5-5](https://evaillant.github.io/lockfree-object-pool/benches/criterion//forward%20msg%20from%20pull%20(nb_writter_5%20nb_readder_5)/report/index.html) //! //! #### 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](https://evaillant.github.io/lockfree-object-pool/benches/criterion/free/report/index.html) and [multithreading](https://evaillant.github.io/lockfree-object-pool/benches/criterion/multi%20thread%20free/report/index.html) mod linear_object_pool; mod linear_owned_reusable; mod linear_page; mod linear_reusable; mod mutex_object_pool; mod mutex_owned_reusable; mod mutex_reusable; mod none_object_pool; mod none_reusable; mod page; mod spin_lock; mod spin_lock_object_pool; mod spin_lock_owned_reusable; mod spin_lock_reusable; pub use linear_object_pool::LinearObjectPool; pub use linear_owned_reusable::LinearOwnedReusable; pub use linear_reusable::LinearReusable; pub use mutex_object_pool::MutexObjectPool; pub use mutex_owned_reusable::MutexOwnedReusable; pub use mutex_reusable::MutexReusable; pub use none_object_pool::NoneObjectPool; pub use none_reusable::NoneReusable; pub use spin_lock_object_pool::SpinLockObjectPool; pub use spin_lock_owned_reusable::SpinLockOwnedReusable; pub use spin_lock_reusable::SpinLockReusable; lockfree-object-pool-0.1.6/src/linear_object_pool.rs000064400000000000000000000054661046102023000206360ustar 00000000000000use crate::{ linear_owned_reusable::LinearOwnedReusable, linear_page::LinearPage, linear_reusable::LinearReusable, }; use std::sync::Arc; /// ObjectPool use a lockfree vector to secure multithread access to pull. /// /// The lockfree vector is implemented as linked list. /// /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// /// let pool = LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct LinearObjectPool { reset: Box, init: Box T + Send + Sync>, head: LinearPage, } impl LinearObjectPool { /// /// Create an new [`LinearObjectPool`] /// /// # Arguments /// * `init` closure to create new item /// * `reset` closure to reset item before reusage /// /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// /// let pool = LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// ``` #[inline] pub fn new(init: I, reset: R) -> Self where R: Fn(&mut T) + 'static + Send + Sync, I: Fn() -> T + 'static + Clone + Send + Sync, { Self { reset: Box::new(reset), init: Box::new(init.clone()), head: LinearPage::new(init), } } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// /// let pool = LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// ``` #[inline] pub fn pull(&self) -> LinearReusable { let (page, page_id) = self.head.alloc(&self.init); unsafe { LinearReusable::new(self, page_id, page) } } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// ``` #[inline] pub fn pull_owned(self: &Arc) -> LinearOwnedReusable { let (page, page_id) = self.head.alloc(&self.init); unsafe { LinearOwnedReusable::new(self.clone(), page_id, page) } } #[inline] pub(crate) fn get_reset_callback(&self) -> &dyn Fn(&mut T) { &self.reset } } lockfree-object-pool-0.1.6/src/linear_owned_reusable.rs000064400000000000000000000046601046102023000213300ustar 00000000000000use crate::linear_object_pool::LinearObjectPool; use crate::page::{Page, PageId}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; /// Wrapper over T used by [`LinearObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct LinearOwnedReusable { pool: Arc>, page_id: PageId, page: *const Page, } impl LinearOwnedReusable { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `page_id` page id /// * `page` page that contains data /// # Safety /// * `page` has to be a valid pointer to a page in `pool` /// * `pool_id` has to be a valid id for `page` #[inline] pub(crate) unsafe fn new( pool: Arc>, page_id: PageId, page: &Page, ) -> Self { Self { pool, page_id, page, } } } impl DerefMut for LinearOwnedReusable { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { unsafe { // SAFETY: there exists only this `LinearOwnedReusable` with this page_id self.page.as_ref().unwrap().get_mut(&self.page_id) } } } impl Deref for LinearOwnedReusable { type Target = T; #[inline] fn deref(&self) -> &Self::Target { unsafe { // SAFETY: there exists only this `LinearOwnedReusable` with this page_id self.page.as_ref().unwrap().get(&self.page_id) } } } impl Drop for LinearOwnedReusable { #[inline] fn drop(&mut self) { unsafe { // SAFETY: there exists only this `LinearOwnedReusable` with this page_id let page = self.page.as_ref().unwrap(); (self.pool.get_reset_callback())(page.get_mut(&self.page_id)); page.free(&self.page_id); } } } unsafe impl Send for LinearOwnedReusable {} // SAFETY: sending the data is allowed if it's Send unsafe impl Sync for LinearOwnedReusable {} // SAFETY: the Mutex manages synchronization so only Send is required lockfree-object-pool-0.1.6/src/linear_page.rs000064400000000000000000000041041046102023000172370ustar 00000000000000use crate::page::{Page, PageId}; use std::ptr; use std::sync::atomic::{AtomicPtr, Ordering}; pub struct LinearPage { page: Page, next: AtomicPtr>, } impl LinearPage { #[inline] pub fn new(init: I) -> Self where I: Fn() -> T, { Self { page: Page::new(init), next: AtomicPtr::new(ptr::null_mut()), } } #[inline] pub fn get_or_create_next(&self, init: I) -> &Self where I: Fn() -> T, { let mut current = self.next.load(Ordering::Relaxed); if current.is_null() { let new = Box::into_raw(Box::new(LinearPage::::new(init))); match self .next .compare_exchange(current, new, Ordering::SeqCst, Ordering::Relaxed) { Ok(_) => { current = new; } Err(x) => { unsafe { // SAFETY: new was been allocated by Box::new drop(Box::from_raw(new)) }; current = x; } } } unsafe { // SAFETY: there are no mutable references to current current.as_ref().unwrap() } } #[inline] pub fn alloc(&self, init: I) -> (&Page, PageId) where I: Fn() -> T + Clone, { let mut linear_page = self; loop { match linear_page.page.alloc() { Some(id) => { return (&linear_page.page, id); } None => { linear_page = linear_page.get_or_create_next(init.clone()); } }; } } } impl Drop for LinearPage { #[inline] fn drop(&mut self) { let current = self.next.load(Ordering::Relaxed); if !current.is_null() { unsafe { // SAFETY: current was allocated with Box::new drop(Box::from_raw(current)) }; } } } lockfree-object-pool-0.1.6/src/linear_reusable.rs000064400000000000000000000041041046102023000201250ustar 00000000000000use crate::linear_object_pool::LinearObjectPool; use crate::page::{Page, PageId}; use std::ops::{Deref, DerefMut}; /// Wrapper over T used by [`LinearObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::LinearObjectPool; /// /// let pool = LinearObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct LinearReusable<'a, T> { pool: &'a LinearObjectPool, page_id: PageId, page: &'a Page, } impl<'a, T> LinearReusable<'a, T> { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `page_id` page id /// * `page` page that contains data /// # Safety /// * `page` has to be a valid pointer to a page in `pool` /// * `pool_id` has to be a valid id for `page` #[inline] pub(crate) unsafe fn new( pool: &'a LinearObjectPool, page_id: PageId, page: &'a Page, ) -> Self { Self { pool, page_id, page, } } } impl<'a, T> DerefMut for LinearReusable<'a, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { unsafe { // SAFETY: there exists only this `LinearReusable` with this page_id self.page.get_mut(&self.page_id) } } } impl<'a, T> Deref for LinearReusable<'a, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { unsafe { // SAFETY: there exists only this `LinearReusable` with this page_id self.page.get(&self.page_id) } } } impl<'a, T> Drop for LinearReusable<'a, T> { #[inline] fn drop(&mut self) { let page = self.page; (self.pool.get_reset_callback())(unsafe { // SAFETY: there exists only this `LinearReusable` with this page_id page.get_mut(&self.page_id) }); page.free(&self.page_id); } } lockfree-object-pool-0.1.6/src/mutex_object_pool.rs000064400000000000000000000060771046102023000205250ustar 00000000000000use crate::{mutex_owned_reusable::MutexOwnedReusable, mutex_reusable::MutexReusable}; use std::mem::ManuallyDrop; use std::sync::{Arc, Mutex}; /// ObjectPool use a [`std::sync::Mutex`] over vector to secure multithread access to pull. /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// /// let pool = MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct MutexObjectPool { objects: Mutex>, reset: Box, init: Box T + Send + Sync>, } impl MutexObjectPool { /// /// Create an new [`MutexObjectPool`] /// /// # Arguments /// * `init` closure to create new item /// * `reset` closure to reset item before reusage /// /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// /// let pool = MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// ``` #[inline] pub fn new(init: I, reset: R) -> Self where R: Fn(&mut T) + Send + Sync + 'static, I: Fn() -> T + Send + Sync + 'static, { Self { objects: Mutex::new(Vec::new()), reset: Box::new(reset), init: Box::new(init), } } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// /// let pool = MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// ``` #[inline] pub fn pull(&self) -> MutexReusable { MutexReusable::new( self, ManuallyDrop::new( self.objects .lock() .unwrap() .pop() .unwrap_or_else(&self.init), ), ) } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// ``` #[inline] pub fn pull_owned(self: &Arc) -> MutexOwnedReusable { MutexOwnedReusable::new( self.clone(), ManuallyDrop::new( self.objects .lock() .unwrap() .pop() .unwrap_or_else(&self.init), ), ) } #[inline] pub(crate) fn attach(&self, mut data: T) { (self.reset)(&mut data); self.objects.lock().unwrap().push(data); } } lockfree-object-pool-0.1.6/src/mutex_owned_reusable.rs000064400000000000000000000030061046102023000212110ustar 00000000000000use crate::mutex_object_pool::MutexObjectPool; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; use std::sync::Arc; /// Wrapper over T used by [`MutexObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct MutexOwnedReusable { pool: Arc>, data: ManuallyDrop, } impl MutexOwnedReusable { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `data` element to wrap #[inline] pub fn new(pool: Arc>, data: ManuallyDrop) -> Self { Self { pool, data } } } impl DerefMut for MutexOwnedReusable { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Deref for MutexOwnedReusable { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.data } } impl Drop for MutexOwnedReusable { #[inline] fn drop(&mut self) { let data = unsafe { // SAFETY: self.data is never referenced again and it isn't dropped ManuallyDrop::take(&mut self.data) }; self.pool.attach(data); } } lockfree-object-pool-0.1.6/src/mutex_reusable.rs000064400000000000000000000027221046102023000200210ustar 00000000000000use crate::mutex_object_pool::MutexObjectPool; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; /// Wrapper over T used by [`MutexObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::MutexObjectPool; /// /// let pool = MutexObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct MutexReusable<'a, T> { pool: &'a MutexObjectPool, data: ManuallyDrop, } impl<'a, T> MutexReusable<'a, T> { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `data` element to wrap #[inline] pub fn new(pool: &'a MutexObjectPool, data: ManuallyDrop) -> Self { Self { pool, data } } } impl<'a, T> DerefMut for MutexReusable<'a, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl<'a, T> Deref for MutexReusable<'a, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.data } } impl<'a, T> Drop for MutexReusable<'a, T> { #[inline] fn drop(&mut self) { let data = unsafe { // SAFETY: self.data is never referenced again and it isn't dropped ManuallyDrop::take(&mut self.data) }; self.pool.attach(data); } } lockfree-object-pool-0.1.6/src/none_object_pool.rs000064400000000000000000000035611046102023000203150ustar 00000000000000use crate::none_reusable::NoneReusable; use std::sync::Arc; /// Basic allocation without pull. Used to compare default rust allocation with different kind of object pool. /// # Example /// ```rust /// use lockfree_object_pool::NoneObjectPool; /// /// let pool = NoneObjectPool::::new(|| Default::default()); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct NoneObjectPool { init: Box T + Send + Sync>, } impl NoneObjectPool { /// /// Create an new [`NoneObjectPool`] /// /// # Arguments /// * `init` closure to create new item /// /// # Example /// ```rust /// use lockfree_object_pool::NoneObjectPool; /// /// let pool = NoneObjectPool::::new(|| Default::default()); /// ``` #[inline] pub fn new(init: I) -> Self where I: Fn() -> T + Send + Sync + 'static, { Self { init: Box::new(init), } } /// /// Create a new element. When the element is dropped, it doesn't return in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::NoneObjectPool; /// /// let pool = NoneObjectPool::::new(|| Default::default()); /// let mut item = pool.pull(); /// ``` #[inline] pub fn pull(&self) -> NoneReusable { NoneReusable::new((self.init)()) } /// /// Create a new element. When the element is dropped, it doesn't return in the pull. /// /// # Example /// ```rust /// use std::sync::Arc; /// use lockfree_object_pool::NoneObjectPool; /// /// let pool = Arc::new(NoneObjectPool::::new(|| Default::default())); /// let mut item = pool.pull_owned(); /// ``` #[inline] pub fn pull_owned(self: &Arc) -> NoneReusable { NoneReusable::new((self.init)()) } } lockfree-object-pool-0.1.6/src/none_reusable.rs000064400000000000000000000017171046102023000176210ustar 00000000000000use std::ops::{Deref, DerefMut}; #[allow(unused_imports)] use crate::none_object_pool::NoneObjectPool; /// Wrapper over T used by [`NoneObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::NoneObjectPool; /// /// let pool = NoneObjectPool::::new(|| Default::default()); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct NoneReusable { data: T, } impl NoneReusable { /// Create new element /// /// # Arguments /// * `data` element to wrappe #[inline] pub fn new(data: T) -> Self { Self { data } } } impl DerefMut for NoneReusable { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Deref for NoneReusable { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.data } } lockfree-object-pool-0.1.6/src/page.rs000064400000000000000000000112221046102023000157040ustar 00000000000000use std::{ cell::UnsafeCell, sync::atomic::{AtomicU32, Ordering}, }; pub struct Page { data: [UnsafeCell; 32], free: AtomicU32, } pub type PageId = u8; impl Page { #[inline] pub fn new(init: I) -> Self where I: Fn() -> T, { Self { data: [ UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), UnsafeCell::new(init()), ], free: AtomicU32::new(u32::MAX), } } #[cfg(test)] pub(crate) fn is_full(&self) -> bool { self.free.load(Ordering::Relaxed) == 0 } #[cfg(test)] pub(crate) fn get_mask(&self) -> u32 { self.free.load(Ordering::Relaxed) } #[inline] pub fn alloc(&self) -> Option { self.free .fetch_update(Ordering::SeqCst, Ordering::Relaxed, |free| { if free == 0 { None } else { Some(free & (free - 1)) } }) .ok() .map(|free| free.trailing_zeros() as u8) } #[inline] pub fn free(&self, id: &PageId) { let mask: u32 = 1 << id; self.free.fetch_or(mask, Ordering::SeqCst); } #[inline] pub unsafe fn get(&self, id: &PageId) -> &T { &*self.data[*id as usize].get() } #[inline] #[allow(clippy::mut_from_ref)] // the function is marked as unsafe for a reason pub unsafe fn get_mut(&self, id: &PageId) -> &mut T { &mut *self.data[*id as usize].get() } } unsafe impl Send for Page {} // normal rules apply unsafe impl Sync for Page {} // normal rules apply #[cfg(test)] mod tests { use super::*; #[test] fn test_page_01() { let page = Page::::new(|| 0); assert!(!page.is_full()); assert_eq!(page.get_mask(), u32::MAX); } #[test] fn test_page_02() { let page = Page::::new(|| 0); let item1 = page.alloc(); assert!(item1.is_some()); assert_eq!(item1.unwrap(), 0); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111110" ); let item2 = page.alloc(); assert!(item2.is_some()); assert_eq!(item2.unwrap(), 1); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111100" ); let item3 = page.alloc(); assert!(item3.is_some()); assert_eq!(item3.unwrap(), 2); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111000" ); page.free(&item2.unwrap()); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111010" ); page.free(&item1.unwrap()); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111011" ); page.free(&item3.unwrap()); assert_eq!( format!("{:b}", page.get_mask()), "11111111111111111111111111111111" ); } #[test] fn test_page_03() { let page = Page::::new(|| 0); for i in 0..32 { assert!(!page.is_full()); let item = page.alloc(); assert!(item.is_some()); assert_eq!(item.unwrap(), i); } assert!(page.is_full()); let item = page.alloc(); assert!(item.is_none()); } } lockfree-object-pool-0.1.6/src/spin_lock.rs000064400000000000000000000040621046102023000167550ustar 00000000000000use std::cell::UnsafeCell; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; pub struct SpinLock { data: UnsafeCell, lock: AtomicBool, } impl SpinLock { #[inline] pub fn new(data: T) -> Self { Self { data: UnsafeCell::new(data), lock: AtomicBool::new(false), } } #[inline] pub fn lock(&self) -> SpinLockGuard { self.acquire(); SpinLockGuard { lock: self } } #[inline] fn acquire(&self) { self.exchange(false, true); } #[inline] fn release(&self) { self.exchange(true, false); } #[inline] fn exchange(&self, from: bool, to: bool) { loop { match self .lock .compare_exchange_weak(from, to, Ordering::SeqCst, Ordering::Relaxed) { Ok(_) => break, Err(_) => { thread::yield_now(); } } } } } unsafe impl Send for SpinLock {} // SAFETY: sending the data is allowed if it's Send unsafe impl Sync for SpinLock {} // SAFETY: the Mutex manages synchronization so only Send is required pub struct SpinLockGuard<'a, T> { lock: &'a SpinLock, } impl<'a, T> DerefMut for SpinLockGuard<'a, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { unsafe { // SAFETY: this is the only active guard &mut *self.lock.data.get() } } } impl<'a, T> Deref for SpinLockGuard<'a, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { unsafe { // SAFETY: this is the only active guard &*self.lock.data.get() } } } impl<'a, T> Drop for SpinLockGuard<'a, T> { #[inline] fn drop(&mut self) { self.lock.release(); } } unsafe impl Send for SpinLockGuard<'_, T> {} // SAFETY: normal rules apply unsafe impl Sync for SpinLockGuard<'_, T> {} // SAFETY: normal rules apply lockfree-object-pool-0.1.6/src/spin_lock_object_pool.rs000064400000000000000000000061531046102023000213370ustar 00000000000000use crate::{ spin_lock::SpinLock, spin_lock_owned_reusable::SpinLockOwnedReusable, spin_lock_reusable::SpinLockReusable, }; use std::mem::ManuallyDrop; use std::sync::Arc; /// ObjectPool use a spin lock over vector to secure multithread access to pull. /// /// The spin lock works like [`std::sync::Mutex`] but /// * use [`std::sync::atomic::AtomicBool`] for synchro /// * active waiting /// /// cf [wikipedia](https://en.wikipedia.org/wiki/Spinlock) for more information. /// /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// /// let pool = SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct SpinLockObjectPool { objects: SpinLock>, reset: Box, init: Box T + Send + Sync>, } impl SpinLockObjectPool { /// /// Create an new [`SpinLockObjectPool`] /// /// # Arguments /// * `init` closure to create new item /// * `reset` closure to reset item before reusage /// /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// /// let pool = SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// ``` #[inline] pub fn new(init: I, reset: R) -> Self where R: Fn(&mut T) + Send + Sync + 'static, I: Fn() -> T + Send + Sync + 'static, { Self { objects: SpinLock::new(Vec::new()), reset: Box::new(reset), init: Box::new(init), } } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// /// let pool = SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// ``` #[inline] pub fn pull(&self) -> SpinLockReusable { SpinLockReusable::new( self, ManuallyDrop::new(self.objects.lock().pop().unwrap_or_else(&self.init)), ) } /// /// Create a new element. When the element is dropped, it returns in the pull. /// /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// ``` #[inline] pub fn pull_owned(self: &Arc) -> SpinLockOwnedReusable { SpinLockOwnedReusable::new( self.clone(), ManuallyDrop::new(self.objects.lock().pop().unwrap_or_else(&self.init)), ) } #[inline] pub(crate) fn attach(&self, mut data: T) { (self.reset)(&mut data); self.objects.lock().push(data); } } lockfree-object-pool-0.1.6/src/spin_lock_owned_reusable.rs000064400000000000000000000030551046102023000220340ustar 00000000000000use crate::spin_lock_object_pool::SpinLockObjectPool; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; use std::sync::Arc; /// Wrapper over T used by [`SpinLockObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// use std::sync::Arc; /// /// let pool = Arc::new(SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// )); /// let mut item = pool.pull_owned(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct SpinLockOwnedReusable { pool: Arc>, data: ManuallyDrop, } impl SpinLockOwnedReusable { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `data` element to wrappe #[inline] pub fn new(pool: Arc>, data: ManuallyDrop) -> Self { Self { pool, data } } } impl DerefMut for SpinLockOwnedReusable { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl Deref for SpinLockOwnedReusable { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.data } } impl Drop for SpinLockOwnedReusable { #[inline] fn drop(&mut self) { let data = unsafe { // SAFETY: self.data is never referenced again and it isn't dropped ManuallyDrop::take(&mut self.data) }; self.pool.attach(data); } } lockfree-object-pool-0.1.6/src/spin_lock_reusable.rs000064400000000000000000000027711046102023000206440ustar 00000000000000use crate::spin_lock_object_pool::SpinLockObjectPool; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; /// Wrapper over T used by [`SpinLockObjectPool`]. /// /// Access is allowed with [`std::ops::Deref`] or [`std::ops::DerefMut`] /// # Example /// ```rust /// use lockfree_object_pool::SpinLockObjectPool; /// /// let pool = SpinLockObjectPool::::new( /// || Default::default(), /// |v| { /// *v = 0; /// } /// ); /// let mut item = pool.pull(); /// /// *item = 5; /// let work = *item * 5; /// ``` pub struct SpinLockReusable<'a, T> { pool: &'a SpinLockObjectPool, data: ManuallyDrop, } impl<'a, T> SpinLockReusable<'a, T> { /// Create new element /// /// # Arguments /// * `pool` object pool owner /// * `data` element to wrappe #[inline] pub fn new(pool: &'a SpinLockObjectPool, data: ManuallyDrop) -> Self { Self { pool, data } } } impl<'a, T> DerefMut for SpinLockReusable<'a, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.data } } impl<'a, T> Deref for SpinLockReusable<'a, T> { type Target = T; #[inline] fn deref(&self) -> &Self::Target { &self.data } } impl<'a, T> Drop for SpinLockReusable<'a, T> { #[inline] fn drop(&mut self) { let data = unsafe { // SAFETY: self.data is never referenced again and it isn't dropped ManuallyDrop::take(&mut self.data) }; self.pool.attach(data); } } lockfree-object-pool-0.1.6/tests/test_generic.rs000064400000000000000000000034121046102023000200200ustar 00000000000000#[macro_export] macro_rules! test_generic_01 { ($name:ident, $expression:expr) => { #[test] fn $name() { let pool = $expression; for _ in 0..2 { let mut v = pool.pull(); assert_eq!(*v, 0); *v += 1; } } }; } #[macro_export] macro_rules! test_generic_02 { ($name:ident, $expression:expr) => { #[test] fn $name() { use std::sync::mpsc; use std::sync::Arc; use std::thread; let pool = Arc::new($expression); let (tx, rx) = mpsc::channel(); let mut children = Vec::new(); for id in 0..5 { let thread_tx = tx.clone(); let thread_pool = Arc::clone(&pool); let child = thread::spawn(move || { let mut msg = thread_pool.pull(); *msg = id; thread_tx.send(*msg).unwrap(); }); children.push(child); } let mut msgs = Vec::new(); for _ in 0..5 { let msg = rx.recv().unwrap(); if !msgs.contains(&msg) && msg < 5 { msgs.push(msg); } } assert_eq!(msgs.len(), 5); for child in children { child.join().unwrap(); } } }; } #[macro_export] macro_rules! test_recycle_generic_01 { ($name:ident, $expression:expr) => { #[test] fn $name() { let pool = $expression; let mut item1 = pool.pull(); *item1 = 5; drop(item1); let item2 = pool.pull(); assert_eq!(*item2, 5); } }; } lockfree-object-pool-0.1.6/tests/test_linear.rs000064400000000000000000000030421046102023000176550ustar 00000000000000use lockfree_object_pool::LinearObjectPool; #[macro_use] mod test_generic; fn make_pool() -> LinearObjectPool { LinearObjectPool::::new(Default::default, |v| { *v = 0; }) } fn make_recycle_pool() -> LinearObjectPool { LinearObjectPool::::new(Default::default, |_v| {}) } test_generic_01!(test_linear_01, make_pool()); test_generic_02!(test_linear_02, make_pool()); test_recycle_generic_01!(test_linear_recycle_01, make_recycle_pool()); #[test] fn test_linear_03() { let pool = make_pool(); let mut addrs = Vec::new(); for _ in 0..10 { let mut v = pool.pull(); assert_eq!(*v, 0); *v += 1; assert_eq!(*v, 1); let o = &mut *v; *o += 1; assert_eq!(*o, 2); let addr = format!("{:?}", o as *const u32); if !addrs.contains(&addr) { addrs.push(addr); } assert_eq!(*v, 2); } assert_eq!(addrs.len(), 1); for _ in 0..2 { let mut v = pool.pull(); assert_eq!(*v, 0); *v += 1; } } #[test] fn test_linear_04() { let pool = make_pool(); let mut addrs = Vec::new(); for _ in 0..10 { let mut v1 = pool.pull(); let mut v2 = pool.pull(); let addr1 = format!("{:?}", &mut *v1 as *const u32); let addr2 = format!("{:?}", &mut *v2 as *const u32); if !addrs.contains(&addr1) { addrs.push(addr1); } if !addrs.contains(&addr2) { addrs.push(addr2); } } assert_eq!(addrs.len(), 2); } lockfree-object-pool-0.1.6/tests/test_mutex.rs000064400000000000000000000007301046102023000175460ustar 00000000000000use lockfree_object_pool::MutexObjectPool; #[macro_use] mod test_generic; fn make_pool() -> MutexObjectPool { MutexObjectPool::::new(Default::default, |v| { *v = 0; }) } fn make_recycle_pool() -> MutexObjectPool { MutexObjectPool::::new(Default::default, |_v| {}) } test_generic_01!(test_mutex_01, make_pool()); test_generic_02!(test_mutex_02, make_pool()); test_recycle_generic_01!(test_mutex_recycle_01, make_recycle_pool()); lockfree-object-pool-0.1.6/tests/test_none.rs000064400000000000000000000003541046102023000173450ustar 00000000000000use lockfree_object_pool::NoneObjectPool; #[test] fn test_none() { let pool = NoneObjectPool::::new(Default::default); for _ in 0..2 { let mut _v = pool.pull(); assert!(*_v == 0); *_v += 1; } } lockfree-object-pool-0.1.6/tests/test_spin_lock.rs000064400000000000000000000007631046102023000203730ustar 00000000000000use lockfree_object_pool::SpinLockObjectPool; #[macro_use] mod test_generic; fn make_pool() -> SpinLockObjectPool { SpinLockObjectPool::::new(Default::default, |v| { *v = 0; }) } fn make_recycle_pool() -> SpinLockObjectPool { SpinLockObjectPool::::new(Default::default, |_v| {}) } test_generic_01!(test_spin_lock_01, make_pool()); test_generic_02!(test_spin_lock_02, make_pool()); test_recycle_generic_01!(test_spin_lock_recycle_01, make_recycle_pool());