refpool/lib.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! A reimplementation of [`std::boxed::Box`][Box] and [`std::rc::Rc`][Rc]
6//! which uses a pool of reusable memory to speed up reallocation.
7//!
8//! # Prerequisites
9//!
10//! In order to initialise a type to its default value from the memory pool
11//! using [`PoolBox::default()`][PoolBox::default] or
12//! [`PoolRef::default()`][PoolRef::default], it needs to implement
13//! [`PoolDefault`][PoolDefault].
14//!
15//! If you want to be able to use [`PoolRef::make_mut()`][PoolRef::make_mut], it
16//! also needs to implement [`PoolClone`][PoolClone].
17//!
18//! For constructing values using [`PoolRef::new()`][PoolRef::new], there's no
19//! requirement.
20//!
21//! There are implementations for [`PoolDefault`][PoolDefault] and
22//! [`PoolClone`][PoolClone] for most primitive types and a good selection of
23//! `std`'s data types, and you can easily provide default implementations for
24//! your own types by implementing the marker trait
25//! [`PoolDefaultImpl`][PoolDefaultImpl]. You can also implement your own if you
26//! have data structures whose memory doesn't need to be fully intitialised at
27//! construction time, which can give you a slight performance boost. (This
28//! optimisation is why [`PoolDefault`][PoolDefault] and
29//! [`PoolClone`][PoolClone] exist as distinct traits, otherwise
30//! [`Default`][Default] and [`Clone`][Clone] would have sufficed.)
31//!
32//! # Usage
33//!
34//! You create new values by calling
35//! [`PoolRef::default(pool)`][PoolRef::default] or [`PoolRef::new(pool,
36//! value)`][PoolRef::new]. This will use memory from the pool if available,
37//! falling back to a normal heap allocation if the pool is empty. When the
38//! last [`PoolRef`][PoolRef] referencing the value is dropped, its allocated
39//! memory is returned to the pool.
40//!
41//! # Differences from [`Box`][Box] and [`Rc`][Rc]
42//!
43//! [`PoolBox`][PoolBox] is API compatible with [`Box`][Box] and [`PoolRef`][PoolRef]
44//! with [`Rc`][Rc], with the following exceptions:
45//!
46//! * Types handled by the pool must be [`Sized`][Sized]. This means the pool
47//! won't accept trait objects, ie. no `Pool<dyn A>`.
48//! * Constructors need a [`Pool`][Pool] argument, so they're necessarily
49//! different: instead of [`Rc::new(value)`][Rc::new], you have
50//! [`PoolRef::default(pool)`][PoolRef::default] to construct a default
51//! value and [`PoolRef::new(pool, value)`][PoolRef::new] as the equivalent
52//! of [`Rc::new(value)`][Rc::new]. Likewise for [`PoolBox`][PoolBox].
53//! * [`PoolBox`][PoolBox] and [`PoolRef`][PoolRef] do not implement
54//! [`Default`][Default], because you need a
55//! [`Pool`][Pool] argument to construct an instance. Use
56//! [`PoolRef::default(pool)`][PoolRef::default].
57//! * There's currently no equivalent to [`Weak`][Weak] for [`PoolRef`][PoolRef].
58//! * Experimental APIs are not implemented.
59//!
60//! # Thread Safety
61//!
62//! [`Pool`][Pool] is strictly thread local, ie. it does not
63//! implement [`Sync`][Sync] and it will fail in appalling ways if you still
64//! somehow manage to access it from two different threads. There is no
65//! equivalent of [`Arc`][Arc] because adding thread safety to the pool turns
66//! out to degrade performance sufficiently that the pool is no longer providing
67//! a significant performance benefit even with the slowest system allocators
68//! you're likely to come across in the wild (by which I mean Windows).
69//!
70//! # Performance
71//!
72//! You can expect [`Pool`][Pool] to always outperform the system allocator,
73//! though the performance gains will vary between platforms. Preliminary
74//! benchmarks show it's approximately twice as fast on Linux, and 5-6 times as
75//! fast on Windows. Custom allocators like jemalloc may yield even less
76//! benefit, but it's very unlikely you'll find an allocator that can outperform
77//! the pool.
78//!
79//! You can expect bigger performance gains from data types with beneficial
80//! [`PoolDefault`][PoolDefault] and [`PoolClone`][PoolClone] implementations,
81//! "beneficial" in this case meaning cases where you can leave most of the
82//! allocated memory uninitialised. [`sized_chunks::Chunk`][Chunk], which
83//! allocates 528 bytes on 64-bit platforms but only needs to initialise 16
84//! of them for [`PoolDefault`][PoolDefault], would be a good example of this.
85
86//! # Example
87//!
88//! ```rust
89//! # use refpool::{Pool, PoolRef};
90//! // Create a pool of `usize` with a max size of 1 (for argument's sake).
91//! let mut pool: Pool<usize> = Pool::new(1);
92//!
93//! {
94//! // Create a reference handle to a usize initialised to 0.
95//! // The pool starts out empty, so this triggers a normal heap alloc.
96//! let value_ref = PoolRef::default(&mut pool);
97//! assert_eq!(0, *value_ref); // You can deref it just like `Rc`.
98//! } // `value_ref` is dropped here, and its heap memory goes on the pool.
99//!
100//! // Check that we do indeed have one allocation in the pool now.
101//! assert_eq!(1, pool.get_pool_size());
102//!
103//! // Create another reference and initialise it to 31337, a good round number.
104//! // This will reuse `value_ref`'s memory.
105//! let another_value_ref = PoolRef::new(&mut pool, 31337);
106//! assert_eq!(31337, *another_value_ref);
107//!
108//! // Check that the pool is again empty after we reused the previous memory.
109//! assert_eq!(0, pool.get_pool_size());
110//! ```
111//!
112//! # Feature Flags
113//!
114//! There's one feature flag available, `default_impl`, which requires a nightly
115//! rustc because it leans on the `min_specialization` language feature, which
116//! removes the `PoolDefaultImpl` trait and instead provides a `default`
117//! overridable implementation for `PoolClone` and `PoolDefault` for any type
118//! that implements `Clone` and `Default`. `PoolDefaultImpl` is an unfortunate
119//! hack to get around the current absence of specialisation in stable rustc.
120//!
121//! [Pool]: struct.Pool.html
122//! [PoolBox]: struct.PoolBox.html
123//! [PoolBox::default]: struct.PoolBox.html#method.default
124//! [PoolRef]: struct.PoolRef.html
125//! [PoolRef::new]: struct.PoolRef.html#method.new
126//! [PoolRef::default]: struct.PoolRef.html#method.default
127//! [PoolRef::make_mut]: struct.PoolRef.html#method.make_mut
128//! [PoolDefault]: trait.PoolDefault.html
129//! [PoolClone]: trait.PoolClone.html
130//! [PoolDefaultImpl]: trait.PoolDefaultImpl.html
131//! [PoolSync]: struct.PoolSync.html
132//! [Box]: https://doc.rust-lang.org/stable/std/boxed/struct.Box.html
133//! [Box::from_raw]: https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.from_raw
134//! [Box::into_raw]: https://doc.rust-lang.org/stable/std/boxed/struct.Box.html#method.into_raw
135//! [Default]: https://doc.rust-lang.org/std/default/trait.Default.html
136//! [Clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html
137//! [Arc]: https://doc.rust-lang.org/std/sync/struct.Arc.html
138//! [Rc]: https://doc.rust-lang.org/std/rc/struct.Rc.html
139//! [Rc::new]: https://doc.rust-lang.org/std/rc/struct.Rc.html#method.new
140//! [Weak]: https://doc.rust-lang.org/std/rc/struct.Weak.html
141//! [Sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html
142//! [Sync]: https://doc.rust-lang.org/std/marker/trait.Sync.html
143//! [Chunk]: https://docs.rs/sized-chunks/*/sized_chunks/sized_chunk/struct.Chunk.html
144
145#![forbid(rust_2018_idioms)]
146#![deny(nonstandard_style)]
147#![warn(
148 unreachable_pub,
149 missing_docs,
150 missing_debug_implementations,
151 missing_doc_code_examples
152)]
153#![cfg_attr(feature = "default_impl", feature(min_specialization))]
154
155use std::mem::MaybeUninit;
156
157mod box_handle;
158mod counter;
159mod pointer;
160mod pool;
161mod ref_handle;
162mod refbox;
163mod stack;
164mod types;
165
166pub mod fakepool;
167
168pub use self::box_handle::PoolBox;
169pub use self::pool::Pool;
170pub use self::ref_handle::PoolRef;
171
172#[cfg(not(feature = "default_impl"))]
173mod std_types;
174#[cfg(not(feature = "default_impl"))]
175pub use self::std_types::PoolDefaultImpl;
176
177/// A trait for initialising a `MaybeUninit<Self>` to a default value.
178pub trait PoolDefault: Default {
179 /// Initialise an instance of `Self` to its default state.
180 ///
181 /// Specifically, after calling `self.default_uninit()`, the object's state
182 /// should be equal to what `<Self as Default>::default()` would produce.
183 ///
184 /// # Safety
185 ///
186 /// You should assume that the object as passed to you contains
187 /// uninitialised memory, and you must leave it in a fully initialised
188 /// state, as expected by `MaybeUninit::assume_init()`.
189 unsafe fn default_uninit(target: &mut MaybeUninit<Self>);
190}
191
192/// A trait for cloning a value into a `MaybeUninit<Self>`.
193pub trait PoolClone: Clone {
194 /// Clone an instance of `Self` into an uninitialised instance of `Self`.
195 ///
196 /// # Safety
197 ///
198 /// You should assume that the object as passed to you contains
199 /// uninitialised memory, and you must leave it in a fully initialised
200 /// state, as expected by `MaybeUninit::assume_init()`.
201 unsafe fn clone_uninit(&self, target: &mut MaybeUninit<Self>);
202}
203
204#[cfg(feature = "default_impl")]
205impl<A> PoolDefault for A
206where
207 A: Default,
208{
209 default unsafe fn default_uninit(target: &mut MaybeUninit<Self>) {
210 target.as_mut_ptr().write(Default::default());
211 }
212}
213
214#[cfg(feature = "default_impl")]
215impl<A> PoolClone for A
216where
217 A: Clone,
218{
219 default unsafe fn clone_uninit(&self, target: &mut MaybeUninit<Self>) {
220 target.as_mut_ptr().write(self.clone());
221 }
222}
223
224#[cfg(test)]
225mod test {
226 use super::*;
227 use std::sync::atomic::{AtomicUsize, Ordering};
228
229 struct DropTest<'a> {
230 counter: &'a AtomicUsize,
231 }
232
233 impl<'a> DropTest<'a> {
234 fn new(counter: &'a AtomicUsize) -> Self {
235 counter.fetch_add(1, Ordering::Relaxed);
236 DropTest { counter }
237 }
238 }
239
240 impl<'a> Drop for DropTest<'a> {
241 fn drop(&mut self) {
242 self.counter.fetch_sub(1, Ordering::Relaxed);
243 }
244 }
245
246 fn fill_drop(pool_size: usize, alloc_size: usize) {
247 let counter = AtomicUsize::new(0);
248 let pool: Pool<DropTest<'_>> = Pool::new(pool_size);
249 {
250 let mut vec = Vec::new();
251 for _ in 0..alloc_size {
252 vec.push(PoolRef::new(&pool, DropTest::new(&counter)));
253 }
254 assert_eq!(alloc_size, counter.load(Ordering::SeqCst));
255 }
256 assert_eq!(0, counter.load(Ordering::SeqCst));
257 }
258
259 #[test]
260 fn dropping_sized() {
261 fill_drop(1024, 2048);
262 }
263
264 #[test]
265 fn dropping_null() {
266 fill_drop(0, 128);
267 }
268
269 #[test]
270 fn allocate_and_deallocate_a_bit() {
271 let pool: Pool<usize> = Pool::new(1024);
272 assert_eq!(0, pool.get_pool_size());
273 let mut refs: Vec<_> = Vec::new();
274 for _ in 0..10000 {
275 refs.push(PoolRef::default(&pool));
276 }
277 assert_eq!(0, pool.get_pool_size());
278 refs.clear();
279 assert_eq!(1024, pool.get_pool_size());
280 for _ in 0..10000 {
281 refs.push(PoolRef::default(&pool));
282 }
283 assert_eq!(0, pool.get_pool_size());
284 let mut refs2 = refs.clone();
285 assert_eq!(refs, refs2);
286 for (left, right) in refs.iter().zip(refs2.iter()) {
287 assert!(PoolRef::ptr_eq(left, right));
288 }
289 refs.clear();
290 assert_eq!(0, pool.get_pool_size());
291 refs2.clear();
292 assert_eq!(1024, pool.get_pool_size());
293 }
294
295 #[test]
296 fn null_pool_antics() {
297 let pool: Pool<usize> = Pool::new(0);
298 assert_eq!(0, pool.get_pool_size());
299 let mut refs: Vec<_> = Vec::new();
300 for _ in 0..10000 {
301 refs.push(PoolRef::default(&pool));
302 }
303 assert_eq!(0, pool.get_pool_size());
304 refs.clear();
305 assert_eq!(0, pool.get_pool_size());
306 for _ in 0..10000 {
307 refs.push(PoolRef::default(&pool));
308 }
309 assert_eq!(0, pool.get_pool_size());
310 let mut refs2 = refs.clone();
311 assert_eq!(refs, refs2);
312 for (left, right) in refs.iter().zip(refs2.iter()) {
313 assert!(PoolRef::ptr_eq(left, right));
314 }
315 refs.clear();
316 assert_eq!(0, pool.get_pool_size());
317 refs2.clear();
318 assert_eq!(0, pool.get_pool_size());
319 }
320
321 #[test]
322 fn unwrap_or_clone() {
323 let pool: Pool<usize> = Pool::new(1024);
324 let val = PoolRef::new(&pool, 1337);
325 // This would crash if unwrap_or_clone tries to drop the consumed PoolRef.
326 let unwrapped = PoolRef::unwrap_or_clone(val);
327 assert_eq!(1337, unwrapped);
328 }
329
330 #[test]
331 fn option_of_ref_size_equals_ref_size() {
332 use std::mem::size_of;
333 assert_eq!(
334 size_of::<PoolRef<usize>>(),
335 size_of::<Option<PoolRef<usize>>>()
336 );
337 assert_eq!(size_of::<Pool<usize>>(), size_of::<Option<Pool<usize>>>());
338 }
339}