#rc #memory-pool

refpool

Efficient memory pool with reference counting

3 unstable releases

✓ Uses Rust 2018 edition

new 0.2.1 Dec 12, 2019
0.2.0 Nov 29, 2019
0.1.0 Nov 26, 2019

#79 in Memory management

34 downloads per month

MPL-2.0+

42KB
827 lines

refpool

A reimplementation of Rust's std::rc::Rc which uses a pool of reusable memory to speed up reallocation.

Is It Fast?

It's about twice as fast as the system allocator on Linux systems, and six times as fast on Windows systems, when the pool is non-empty. For certain data types, gains can be even higher.

Documentation

Licence

Copyright 2019 Bodil Stokke

This software is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

Code of Conduct

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.


lib.rs:

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:

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 unitialised. sized_chunks::Chunk, which allocates 528 bytes on 64-bit platforms but only needs to initialise 16 bytes for PoolDefault, would be a good example of this.

Example

# use refpool::{Pool, PoolRef};
// 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());

Dependencies