[−][src]Crate refpool
A reimplementation of std::rc::Rc which uses a pool of reusable
memory to speed up reallocation.
Prerequisites
In order to initialise a type to its default value from the memory pool
using PoolRef::default(), it needs to implement
PoolDefault.
If you want to be able to use PoolRef::make_mut(), it
also needs to implement PoolClone.
For constructing values using PoolRef::new(), there's no
requirement.
There are implementations for PoolDefault and
PoolClone for most primitive types and a good selection of
std's data types, and you can easily provide default implementations for
your own types by implementing the marker trait
PoolDefaultImpl. You can also implement your own if you
have data structures whose memory doesn't need to be fully intitialised at
construction time, which can give you a slight performance boost. (This
optimisation is why PoolDefault and
PoolClone exist as distinct traits, otherwise
Default and Clone would have sufficed.)
Usage
You create new values by calling
PoolRef::default(pool) or PoolRef::new(pool, value). This will use memory from the pool if available,
falling back to a normal heap allocation if the pool is empty. When the
last PoolRef referencing the value is dropped, its allocated
memory is returned to the pool.
Differences from Rc and Arc
PoolRef is API compatible with Rc, with the following
exceptions:
- Types handled by the pool must be
Sized. This means the pool won't accept trait objects, ie. noPool<dyn A>. - Constructors need a
Poolargument, so they're necessarily different: instead ofRc::new(value), you havePoolRef::default(pool)to construct a default value andPoolRef::new(pool, value)as the equivalent ofRc::new(value). - It does not implement
Default, because you need aPoolargument to construct an instance. UsePoolRef::default(pool). - There's currently no equivalent to
Weak. - Experimental APIs are not implemented.
Thread Safety
Pool defaults to being thread local by default, ie. it does not
implement Sync and it will fail in appalling ways if you still
somehow manage to access it from two different threads. There's a marker
type PoolSync, available behind the sync feature flag, which
you can pass as a second type argument to Pool and
PoolRef, for a thread safe version. However, this will be much
less performant, on some platforms even failing to outperform the system
allocator by a significant margin. It's not recommended that you use pools
for thread safe code unless your benchmarks actually show that you gain from
doing so.
There are also type aliases for the thread safe version available in the
refpool::sync namespace, if you have the sync feature flag enabled:
refpool::sync::Pool<A> and refpool::sync::PoolRef<A>.
Performance
You can expect Pool to always outperform the system allocator,
though the performance gains will vary between platforms. Preliminary
benchmarks show it's approximately twice as fast on Linux, and 5-6 times as
fast on Windows. The PoolSync version is marginally faster on
Windows, but about 3 times slower on Linux, hence the recommendation above
that you don't use it without benchmarks to back your use case.
You can expect bigger performance gains from data types with beneficial
PoolDefault and PoolClone implementations,
"beneficial" in this case meaning cases where you can leave most of the
allocated memory uninitialised. sized_chunks::Chunk, which
allocates 528 bytes on 64-bit platforms but only needs to initialise 16
of them for PoolDefault, would be a good example of this.
Example
// Create a pool of `usize` with a max size of 1 (for argument's sake). let mut pool: Pool<usize> = Pool::new(1); { // Create a reference handle to a usize initialised to 0. // The pool starts out empty, so this triggers a normal heap alloc. let value_ref = PoolRef::default(&mut pool); assert_eq!(0, *value_ref); // You can deref it just like `Rc`. } // `value_ref` is dropped here, and its heap memory goes on the pool. // Check that we do indeed have one allocation in the pool now. assert_eq!(1, pool.get_pool_size()); // Create another reference and initialise it to 31337, a good round number. // This will reuse `value_ref`'s memory. let another_value_ref = PoolRef::new(&mut pool, 31337); assert_eq!(31337, *another_value_ref); // Check that the pool is again empty after we reused the previous memory. assert_eq!(0, pool.get_pool_size());
Structs
| Pool | A pool of preallocated memory sized to match type |
| PoolRef | A reference counted pointer to |
Traits
| PoolClone | A trait for cloning a value into a |
| PoolDefault | A trait for initialising a |
| PoolDefaultImpl | A marker trait for types which should be fully initialised. |