#slab #allocator

obstack

A fast, stack-based allocator, usable for any object

5 releases

Uses old Rust 2015

0.1.4 Sep 4, 2020
0.1.3 Aug 31, 2017
0.1.2 Aug 31, 2017
0.1.1 Aug 30, 2017
0.1.0 Aug 30, 2017

#210 in Memory management

27 downloads per month

MIT/Apache

28KB
521 lines

rust-obstack

A fast segmented stack allocator, supporting multiple objects of any type.

A type of arena allocator, obstacks deallocate memory all at once, when the Obstack itself is destroyed. The benefit is extremely fast allocation: just a pointer bump. Unlike a typed arena, a single Obstack can contain values with any number of different types.

For Copy types pushing a value to an Obstack returns a standard mutable reference:

let r: &mut u8 = stack.push_copy(42);
assert_eq!(*r, 42);

As Copy types can't implement Drop, nothing needs to be done to deallocate the value beyond deallocating the memory itself, which is done en-mass when the Obstack itself is dropped. push_copy() is thus limited to types that implement Copy.

Types that do not implement Copy may implement Drop. As Rust's type system doesn't have a negative !Drop trait, Obstack has a second method - push() - that is not restricted to Copy types. This method returns the wrapper type Ref<T>, that wraps the underlying mutable reference. This wrapper owns the value on the stack, and ensures that drop is called when the wrapper goes out of scope. Essentially Ref is the equivalent of Box, but using an Obstack rather than the heap.

In practice even when the Ref wrapper is used, if the underlying type doesn't actually implement a meaningful drop method, the Rust compiler is able to optimize away all calls to drop, resulting in the same performance as for Copy types; the actual Ref::drop() method is [inline(always)] to aid this process. This is important as not all non-drop types can implement Copy - notably mutable references can't.

Obstack allocates memory as a segmented stack consisting of one or more segments of contiguous memory. Each time the top segment becomes full, a new segment is allocated from the heap. To ensure the total number of allocations remains small, segments are allocated in a powers-of-two fashion, with each segment being twice the size of the previous one.

Once a segment has been allocated it's stable for the life of the Obstack, allowing the values contained in that segment to be referenced directly; Rust lifetimes ensure that the references are valid for the lifetime of the Obstack.

Benchmarks

Some preliminary benchmarks can be run with:

cargo bench

tl;dr: Approximately 10x faster to allocate and deallocate a linked-list.

No runtime deps