Arena allocator with scopes

Overview

Scoped-Arena

crates docs actions MIT/Apache loc

Scoped-Arena provides arena allocator with explicit scopes.

Arena allocation

Arena allocators are simple and provides ludicrously fast allocation.
Basically allocation requires only increment of internal pointer in the memory block to alignment of allocated object and then to size of allocated object and that's it.
When memory block is exhausted arena will allocate new bigger memory block.
Then arena can be reset after all allocated objects are not used anymore, keeping only last memory block and reuse it.
After several warmup iterations the only memory block is large enough to handle all allocations until next reset.

Example

use scoped_arena::Scope;

struct Cat {
    name: String,
    hungry: bool,
}

/// Create new arena with `Global` allocator.
let mut scope = Scope::new();

/// Construct a cat and move it to the scope.
let cat: &mut Cat = scope.to_scope(Cat {
    name: "Fluffy".to_owned(),
    hungry: true,
});

// Now `cat` is a mutable reference bound to scope borrow lifetime.

assert_eq!(&cat.name, "Fluffy");
assert!(cat.hungry);

cat.hungry = false;

// This cat instance on scope will be automatically dropped when `scope` is dropped or reset.
// It is impossible to reset before last usage of `cat`.

// Next line will drop cat value and free memory occupied by it.
scope.reset();

// If there were more cats or any other objects put on scope they all would be dropped and memory freed.

Scopes

To reuse memory earlier this crates provides Scope with methods to create sub-Scopes.
When sub-Scope is reset or dropped it will Drop all stored values and free memory allocated by the scope and flush last of new allocated memory block into parent.
While objects allocated with parent Scope are unchanged and still valid.

Well placed scopes can significantly reduce memory consumption.
For example if few function calls use a lot of dynamic memory but don't need it to be available in caller
they can be provided with sub-scope.
At the same time any memory allocated in parent scope stays allocated.

Creating sub-scope is cheap and allocating within sub-scope is as fast as allocating in parent scope.\

Example

use scoped_arena::{Scope, ScopeProxy};


fn heavy_on_memory(mut scope: Scope<'_>, foobar: &String) {
    for _ in 0 .. 42 {
        let foobar: &mut String = scope.to_scope(foobar.clone());
    }

    // new `scope` is dropped here and drops all allocated strings and frees memory.
}

let mut scope = Scope::new();

// Proxy is required to be friends with borrow checker.
// Creating sub-scope must lock parent `Scope` from being used, which requires mutable borrow, but any allocation borrows `Scope`.
// `Proxy` relaxes this a bit. `Proxy` borrows `Scope` mutably and tie allocated objects lifetime to scopes' borrow lifetime.
// So sub-scope can borrow proxy mutably while there are objects allocated from it.
let mut proxy = scope.proxy();

let foobar: &mut String = proxy.to_scope("foobar".to_owned());

// Make sub-scope for the call.
heavy_on_memory(proxy.scope(), &*foobar);

// If `heavy_on_memory` didn't trigger new memory object allocation in the scope,
// sub-scope drop would rewind scope's internals to exactly the same state.
// Otherwise last of new blocks will become current block in parent scope.
//
// Note that `foobar` is still alive.

heavy_on_memory(proxy.scope(), &*foobar);
heavy_on_memory(proxy.scope(), &*foobar);
heavy_on_memory(proxy.scope(), &*foobar);
heavy_on_memory(proxy.scope(), &*foobar);

// Once peak memory consumption is reached, any number of `heavy_on_memory` calls would not require new memory blocks to be allocated.
// Even `loop { heavy_on_memory(proxy.scope(), &*foobar) }` will settle on some big enough block.

Dropping

to_scope and try_to_scope methods store drop-glue for values that needs_drop. On reset or drop scope iterates and properly drops all values. No drop-glue is added for types that doesn't need drop. Scope allocates enough memory and writes value there, no bookkeeping overhead.

Iterator collecting

to_scope_from_iter method acts as to_scope but works on iterators and returns slices. The limitation is that to_scope_from_iter need to allocate memory enough for upper bound of what iterator can yield. If upper bound is too large or iterator is unbounded it will always fail. One can use try_to_scope_from_iter so fail is Err and not panic. It is safe for iterator to yield more items then upper bound it reports, to_scope_from_iter would not iterate past upper bound. On success it returns mutable reference to slice with items from iterator in order. All values will be dropped on scope reset or drop, same as with to_scope.

This method is especially useful to deal with API that requires slices (glares at FFI), collecting into temporary Vec would cost much more.

#![no_std] Support

Scoped-Arena is a no_std crate. It depends only core crates and optionally on alloc crate.

Nightly Rust feature(allocator_api) Support

Scoped-Arena uses copy of allocator_api traits and types for underlying allocator.
On nightly channel it is possible to enable allocator_api feature for this crate. Then actual allocator_api traits and types will be used instead, enabling using any compatible allocator. Additionally it &Arena, &Scope and ScopeProxy will implement core::alloc::Allocator making them suitable for standard rust collections.

Note that as rust allocator_api feature is unstable, this crate's allocator_api feature is also considered unstable and may not follow semver. That is, changes to follow allocator_api modifications in rust can be published with patch version, although they must not break code that does not use the feature.

License

Licensed under either of

at your option.

Contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

You might also like...
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Portable linked-list allocator designed for baremetal systems

Palloc Portable linked-list allocator for embedded / baremetal systems. Using the crate Include this in the [dependencies] section of Cargo.toml pallo

Custom memory allocator that helps discover reads from uninitialized memory

libdiffuzz: security-oriented alternative to Memory Sanitizer This is a drop-in replacement for OS memory allocator that can be used to detect uses of

The Wasm-Enabled, Elfin Allocator

wee_alloc The Wasm-Enabled, Elfin Allocator API Docs | Contributing | Chat Built with 🦀 🕸 by The Rust and WebAssembly Working Group About wee_alloc:

A simple allocator written in Rust that manages memory in fixed-size chunks.

Simple Chunk Allocator A simple no_std allocator written in Rust that manages memory in fixed-size chunks/blocks. Useful for basic no_std binaries whe

🌋 A very lightweight wrapper around the Vulkan Memory Allocator 🦀

🌋 vk-mem-alloc-rs A very lightweight wrapper around the Vulkan Memory Allocator 🦀 [dependencies] vk-mem-alloc = "0.1.1" Simple Vulkan Memory Allocat

The Solid-State Register Allocator

The Solid-State Register Allocator A simple, extremely fast, reverse linear scan register allocator. See the detailed write-up for an in-depth explana

General purpose memory allocator written in Rust.

Memalloc Memory allocator written in Rust. It implements std::alloc::Allocator and std::alloc::GlobalAlloc traits. All memory is requested from the ke

Executable memory allocator with support for dual mapping and W^X protection

jit-allocator A simple memory allocator for executable code. Use JitAllocator type to allocate/release memory and virtual_memory module functions to e

A tree-backed slab allocator

beton A tree-backed slab allocator API Docs | Releases | Contributing Installation $ cargo add beton Memory Safety This crate uses unsafe operations i

Comments
  • Dropping a Scope may invoke undefined behavior

    Dropping a Scope may invoke undefined behavior

    Since Scope allows you to create arbitrary circular references, there isn't a generally safe order that values can be dropped which ensures that "live" values never have a reference to a "dead" value.

    Example

    use scoped_arena::Scope;
    
    struct DropCounter<'a>(&'a mut Box<u32>);
    
    impl<'a> Drop for DropCounter<'a> {
        fn drop(&mut self) {
            **self.0 -= 1;
        }
    }
    
    fn main() {
        let scope = Scope::new();
        let count_a = scope.to_scope(Box::new(1));
        let counter = scope.to_scope(DropCounter(count_a));
        let count_b = scope.to_scope(Box::new(1));
        counter.0 = count_b;
        drop(scope);
    }
    

    Output

    error: process didn't exit successfully: `target\debug\scoped-arena-test.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)
    
    opened by dzamkov 3
Owner
Zakarum
Zakarum
Fast, concurrent, arena-based allocator with drop support

blink-alloc Blink-alloc is extremely fast allocator based on the common idea of allocating linearly zipping a cursor through memory chunk and reset ev

Zakarum 55 Mar 5, 2023
Simple profiler scopes for wgpu using timer queries

wgpu-profiler Simple profiler scopes for wgpu using timer queries Features Easy to use profiler scopes Allows nesting! Can be disabled by runtime flag

null 41 Dec 5, 2022
A doubly-linked list for Rust backed by generational-arena.

generational_token_list A doubly-linked list backed by generational-arena. Inspired by indexlist. Instead of returning pointers or numerical indices t

null 9 Dec 10, 2022
A compact generational arena data structure for Rust.

Compact Generational Arena This crate provides Arena<T>, a contiguous growable container which assigns and returns IDs to values when they are added t

Chevy Ray Johnston 17 Dec 6, 2022
A generational arena based LRU Cache implementation in 100% safe rust.

generational-lru Crate providing a 100% safe, generational arena based LRU cache implementation. use generational_lru::lrucache::{LRUCache, CacheError

Arindam Das 37 Dec 21, 2022
A top-down arena shooter roguelite in which you're a mythical marshmallow god fighting against peasant munchies such as chocolates, jellies, or candies!

Mythmellow A top-down arena shooter roguelite in which you're a mythical marshmallow god fighting against peasant munchies such as chocolates, jellies

Umut 3 Oct 16, 2023
A top-down arena shooter roguelite in which you're a mythical marshmallow god fighting against peasant munchies such as chocolates, jellies, or candies!

Mythmallow A top-down arena shooter roguelite in which you're a mythical marshmallow god fighting against peasant munchies such as chocolates, jellies

Umut 3 Oct 29, 2023
Exploration of using Storage instead of Allocator to parameterize collections in Rust

storage-poc aims at exploring the usage of custom Storages, rather than custom Allocators. Goals This is a Proof-of-Concept aiming at: Demonstrating t

null 106 Dec 8, 2022
Custom memory allocator that helps discover reads from uninitialized memory

libdiffuzz: security-oriented alternative to Memory Sanitizer This is a drop-in replacement for OS memory allocator that can be used to detect uses of

Sergey 155 Dec 3, 2022
global allocator that provides hooks for tracking allocation events

tracking-allocator A GlobalAlloc-compatible allocator implementation that provides the ability to track allocation events. examples As allocators are

Toby Lawrence 39 Dec 22, 2022