diff --git a/toys-rs/Cargo.toml b/toys-rs/Cargo.toml index e289e51..cab920f 100755 --- a/toys-rs/Cargo.toml +++ b/toys-rs/Cargo.toml @@ -14,4 +14,4 @@ criterion = { version = "0.4"} [[bench]] name = "benchmark" -harness = false \ No newline at end of file +harness = false diff --git a/toys-rs/benches/benchmark.rs b/toys-rs/benches/benchmark.rs index f2bbe55..6ab456a 100755 --- a/toys-rs/benches/benchmark.rs +++ b/toys-rs/benches/benchmark.rs @@ -3,8 +3,9 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use toys_rs::localpool::Pool; use rand::random; -fn criterion_benchmark(c: &mut Criterion) { - let mut p:Pool<[u8;65536]>=unsafe{Pool::new()}; +fn bench_pool(c: &mut Criterion) { + let mut p:Pool<[u64;16]>=Pool::new(); + p.reserve(8192); c.bench_function("pool one-shot", |b| b.iter(|| { black_box(p.get()); })); @@ -16,7 +17,25 @@ fn criterion_benchmark(c: &mut Criterion) { } })); println!("{} in use, {} idling", v.len(), p.idle()); + p.release(1024); } -criterion_group!(benches, criterion_benchmark); +#[allow(deprecated)] +fn _bench_thinpool(c: &mut Criterion) { + use toys_rs::thinpool::Pool; + let mut p:Pool=unsafe{Pool::new()}; + c.bench_function("thinpool one-shot", |b| b.iter(|| { + black_box(p.get()); + })); + let mut v=Vec::new(); + c.bench_function("thinpool random", |b| b.iter(|| { + match random::(){ // 2ns + true=>{v.push(p.get());}, // 2ns+4ns + false=>{black_box(v.pop());} + } + })); + println!("{} in use, {} idling", v.len(), p.idle()); +} + +criterion_group!(benches, bench_pool); criterion_main!(benches); \ No newline at end of file diff --git a/toys-rs/src/lib.rs b/toys-rs/src/lib.rs index 52ebac4..d692782 100755 --- a/toys-rs/src/lib.rs +++ b/toys-rs/src/lib.rs @@ -1,5 +1,7 @@ //! Rust toy libraries pub mod mem; pub mod localpool; +#[deprecated="benchmark shows terrible performance"] +pub mod thinpool; #[deprecated] pub mod locallock; \ No newline at end of file diff --git a/toys-rs/src/localpool.rs b/toys-rs/src/localpool.rs index c7130a7..3e2ced7 100755 --- a/toys-rs/src/localpool.rs +++ b/toys-rs/src/localpool.rs @@ -9,8 +9,6 @@ Abandoned: - variable `INIT_SIZE`: rust still has inferring bugs for generic const */ -const INIT_SIZE:usize=8; // init size - struct RawPool<'a, T>{ // `'a`: lifetime for `newfn` pool: Vec<*mut T>, newfn: Box*mut T+'a> @@ -66,22 +64,17 @@ impl<'a, T> Pool<'a, T>{ #[deprecated="use drop instead"] pub fn put(&mut self, _: PoolBox){} - fn with_new(mut newfn: New)->Self + fn with_new(newfn: New)->Self where New:FnMut()->*mut T+'a{ - let mut pool=Vec::with_capacity(INIT_SIZE); - for _ in 0..INIT_SIZE { - pool.push(newfn()); - } + let pool=Vec::new(); Pool(Rc::new(UnsafeCell::new(RawPool{ pool, newfn: Box::new(newfn)}))) } /// Constructs a new object Pool which provides empty `T` objeccts. /// # Unsafe /// Object are uninitialized. - pub unsafe fn new()->Self{ - Self::with_new(||{ - unsafe{mem::new()} - }) + pub fn new()->Self{ + Self::with_new(||unsafe{mem::new()}) } /// Constructs a new object Pool which provides `T` objeccts @@ -117,6 +110,37 @@ impl<'a, T> Pool<'a, T>{ } pub fn idle(&self)->usize{self.inner().pool.len()} + + /// Reserves idle objects for at least additional more items + /// to be got from the given Pool. + pub fn reserve(&mut self, additional: usize){ + let p=self.inner(); + p.pool.resize_with( + p.pool.len()+additional, + &mut *p.newfn + ) + } + + /// release `n` idling objects + pub fn release(&mut self, n: usize){ + let pool=&mut self.inner().pool; + unsafe{ + if n>pool.len(){ // release all + for i in 0..pool.len(){ + mem::delete(*pool.get_unchecked(i)); + } + pool.set_len(0); + } else { // release n + for i in pool.len()-n..pool.len(){ + mem::delete(*pool.get_unchecked(i)); + } + pool.set_len(pool.len()-n); + }} + // shrink vector to half + if pool.capacity()>64 && pool.len()>2{ + pool.shrink_to(pool.len()<<1); + } + } } /// drop will only be called when Rc counter returns 0 @@ -178,7 +202,7 @@ mod tests { let mut p = Pool::with_init( |x|{*x=counter; counter+=1;} ); - assert_eq!(*p.get(), INIT_SIZE); + assert_eq!(*p.get(), 1); drop(p); } @@ -195,17 +219,31 @@ mod tests { use super::*; let mut x=1; let mut p=Pool::with_generator(||{let y=x; x+=1; y}); - assert_eq!(*p.get(), INIT_SIZE); + assert_eq!(*p.get(), 1); } #[test] fn test_clone(){ use super::*; - let p:Pool=Pool::with_init(|_|{}); + let p:Pool=Pool::new(); let mut p1=p.clone(); drop(p1.get()); // make sure p1 not stripped } + #[test] + fn test_reserve_release(){ + use super::*; + use std::hint::black_box; + let mut p:Pool=Pool::new(); + black_box(*p.get()); + p.release(2); // does nothing + black_box(*p.get()); + p.reserve(2); + assert_eq!(p.idle(),3); + p.release(2); + assert_eq!(p.idle(),1); + black_box(*p.get()); + } #[test] fn test_tokio(){ diff --git a/toys-rs/src/mem.rs b/toys-rs/src/mem.rs index 0b22f1e..14bbce0 100755 --- a/toys-rs/src/mem.rs +++ b/toys-rs/src/mem.rs @@ -89,4 +89,4 @@ pub unsafe fn resize_arr(ptr: *mut T, oldlen: usize, newlen: usize)->*mut T{ /// Returns a `*mut T` null pointer #[inline(always)] -pub const fn nullptr()->*mut T{0 as *mut T} \ No newline at end of file +pub const fn nullptr()->*mut T{0 as *mut T} diff --git a/toys-rs/src/thinpool.rs b/toys-rs/src/thinpool.rs new file mode 100755 index 0000000..2228b59 --- /dev/null +++ b/toys-rs/src/thinpool.rs @@ -0,0 +1,234 @@ +//! A simple single-thread object pool, allocated in bundle. + +struct ThinBundle{ // SAFETY: must be pinned + space: [T; usize::BITS as usize], // [T; 64] + idle: usize, // idling flat, 1 for idle and 0 for occupied + pool: *mut Vec<*mut ThinBundle>, +} + +struct RawPool<'a, T>{ // lifetime for new function + pool: Vec<*mut ThinBundle>, // never include fully occupied bundle + newfn: Box*mut ThinBundle+'a> +} + +#[derive(Clone)] +pub struct Pool<'a, T> + (Rc>>); + +pub struct PoolBox{ + space: *mut T, + bundle: *mut ThinBundle, +} + +impl<'a, T> Pool<'a, T>{ + fn inner(&self)->&mut RawPool<'a, T>{ + // SAFETY: within one thread, only one mut can be got + unsafe{&mut *self.0.get()} + } + + /// Gets an object from pool + pub fn get(&mut self)->PoolBox{ + let x=self.inner(); + match x.pool.last_mut(){ + None=>unsafe{ // new and return + let bundle=(*x.newfn)(); + let space=(*bundle).space.as_mut_ptr(); + (*bundle).idle=(!0)^1; // 0b1..10 + (*bundle).pool=&mut x.pool; + let output=PoolBox{space, bundle}; + x.pool.push(bundle); + output + }, + Some(bundle)=>unsafe{ + let bundle=*bundle; + // SAFETY: idle is never 0 + let offset=(*bundle).idle.trailing_zeros() as usize; + let space=(*bundle).space.as_mut_ptr().add(offset); + (*bundle).idle^=1<){} // automatically call drop + + fn with_new(newfn: New)->Self + where New:FnMut()->*mut ThinBundle+'a{ + let pool=Vec::new(); + Pool(Rc::new(UnsafeCell::new(RawPool{ pool, newfn: Box::new(newfn)}))) + } + + /// Constructs a new object Pool which provides empty `T` objeccts. + /// # Unsafe + /// Object are uninitialized. + pub unsafe fn new()->Self{ + Self::with_new(|| mem::new()) + } + + /// Constructs a new object Pool which provides `T` objeccts + /// initialized with `init(&mut T)`. + /// # Unsafe + /// Object may be uninitialized. + pub fn with_init (mut init: Init)->Self + where Init:FnMut(&mut T)+'a{ + Self::with_new(move||{unsafe{ + let bundle: *mut ThinBundle=mem::new(); + let space=(*bundle).space.as_mut_ptr(); + for i in 0..(usize::BITS as usize){ + init(&mut *space.add(i)); + } + bundle + }}) + } + + /// Constructs a new object Pool which provides `T` objeccts + /// generated with `generate()->T`. + pub fn with_generator(mut generate: Generator)->Self + where Generator:FnMut()->T+'a{ + Self::with_new(move||{unsafe{ + let bundle: *mut ThinBundle=mem::new(); + let space=(*bundle).space.as_mut_ptr(); + for i in 0..(usize::BITS as usize){ + space.add(i).write(generate()); + } + bundle + }}) + } + + /// Constructs a new object Pool which provides `T` objeccts + /// cloned from `value:T where T:Clone`. + /// + /// SAFETY: value should outlive pool itself + pub fn with_value(value: T)->Self where T:Clone+'a{ + Self::with_generator(move || value.clone()) + } + + pub fn idle(&self)->usize{ + self.inner().pool.iter().map(|x|unsafe{(**x).idle.count_ones() as usize}).sum() + } +} + +/// drop will only be called when Rc counter returns 0 +impl<'a, T> Drop for RawPool<'a, T>{ + fn drop(&mut self) { + for x in &self.pool{ + unsafe{mem::delete(*x)} + } + } +} + + +impl std::ops::Deref for PoolBox{ + type Target = T; + fn deref(&self) -> &Self::Target { + // SAFETY: self.value will never be null + unsafe{& *self.space} + } +} +impl std::ops::DerefMut for PoolBox{ + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: self.value will never be null + unsafe{&mut *self.space} + } +} +impl Drop for PoolBox{ + fn drop(&mut self) {unsafe{ + // SAFETY: push is runned on local thread + let bundle=self.bundle; + let offset=self.space.offset_from((*bundle).space.as_ptr()); + if (*bundle).idle==0 { + (*(*bundle).pool).push(bundle); + } + (*self.bundle).idle^=1< Sync for PoolBox{} + + +#[allow(deprecated)] +#[cfg(test)] +mod tests { + + // #[test] + fn _test_init(){ + use super::*; + let mut counter=1; + let mut p = Pool::with_init( + |x|{*x=counter; counter+=1;} + ); + let g1=p.get(); + let g2=p.get(); + assert_eq!(*g1, 1); + assert_eq!(*g2, 2); + drop(p); + } + + // #[test] + fn _test_value(){ + use super::*; + let s=String::from("hello"); + let mut p:Pool<_>=Pool::with_value(&s); + assert_eq!(*p.get(), "hello"); + } + + // #[test] + fn _test_gen(){ + use super::*; + let mut x=1; + let mut p:Pool<_>=Pool::with_generator(||{let y=x; x+=1; y}); + assert_eq!(*p.get(), 1); + } + + // #[test] + fn _test_clone(){ + use super::*; + let p:Pool=Pool::with_init(|_|{}); + let mut p1=p.clone(); + drop(p1.get()); // make sure p1 not stripped + } + + + // #[test] + fn _test_tokio(){ + use tokio::{ + runtime::Builder, + task::{LocalSet, spawn_local, yield_now}, + }; + use super::*; + async fn sleepygreeting<'a>(mut pool: Pool<'a, i32>){ + for _ in 0..5{ + let x=pool.get(); + if true==rand::random(){ + yield_now().await; + } + println!("Get {} from pool!", *x); + } + } + async fn tokio_main(){ + let mut ipool=0; + let pool = Pool::with_generator(move||{ipool+=1; ipool}); + let mut tasks = Vec::new(); + for _ in 0..5{ + tasks.push(spawn_local( + sleepygreeting(pool.clone()) + )); + } + for t in tasks{ + let _ = t.await; + } + } + Builder::new_current_thread().enable_time().build().unwrap().block_on( + LocalSet::new().run_until(tokio_main()) + ); + } + +} + +use std::{cell::UnsafeCell, rc::Rc}; + +use crate::mem; \ No newline at end of file