A fast bump allocator that supports allocation scopes / checkpoints. Aka an arena for values of arbitrary types.

Overview

bump-scope

Crates.io Documentation Rust License Build Status

A fast bump allocator that supports allocation scopes / checkpoints. Aka an arena for values of arbitrary types.

What is bump allocation?

A bump allocator owns a big chunk of memory. It has a pointer that starts at one end of that chunk. When an allocation is made that pointer gets aligned and bumped towards the other end of the chunk by the allocation's size. When its chunk is full, it allocates another chunk with twice the size.

This makes allocations very fast. The drawback is that you can't reclaim memory like you do with a more general allocator. Memory for the most recent allocation can be reclaimed. You can also use scopes, checkpoints and reset to reclaim memory.

A bump allocator is great for phase-oriented allocations where you allocate objects in a loop and free them at the end of every iteration.

use bump_scope::Bump;
let mut bump: Bump = Bump::new();

loop {
    // use bump ...
    bump.reset();
}

The fact that the bump allocator allocates ever larger chunks and reset only keeps around the largest one means that after a few iterations, every bump allocation will be done on the same chunk and no more chunks need to be allocated.

The introduction of scopes makes this bump allocator also great for temporary allocations and stack-like usage.

Comparison to bumpalo

Bumpalo is a popular crate for bump allocation. This crate was inspired by bumpalo and Always Bump Downwards.

Unlike bumpalo, this crate...

  • Supports scopes and checkpoints.
  • Drop is always called for allocated values unless explicitly leaked or forgotten.
    • alloc* methods return a BumpBox<T> which owns and drops T. Types that don't need dropping can be turned into references with into_ref and into_mut.
  • You can allocate a slice from any Iterator with alloc_iter.
  • Every method that panics on allocation failure has a fallible try_* counterpart.
  • Bump's base allocator is generic.
  • Bump and BumpScope have the same repr as NonNull<u8>. (vs 3x pointer sized)
  • Won't try to allocate a smaller chunk if allocation failed.
  • No built-in allocation limit. You can provide an allocator that enforces an allocation limit (see tests/limit_memory_usage.rs).
  • Allocations are a bit more optimized. (see crates/inspect-asm/out/x86-64 and benchmarks)
  • You can choose the bump direction. Bumps upwards by default.
  • You can choose the minimum alignment.

Scopes and Checkpoints

You can create scopes to make allocations that live only for a part of its parent scope. Creating and exiting scopes is virtually free. Allocating within a scope has no overhead.

You can create a new scope either with a scoped closure or with a scope_guard:

use bump_scope::Bump;

let mut bump: Bump = Bump::new();

// you can use a closure
bump.scoped(|mut bump| {
    let hello = bump.alloc_str("hello");
    assert_eq!(bump.stats().allocated(), 5);
    
    bump.scoped(|bump| {
        let world = bump.alloc_str("world");

        println!("{hello} and {world} are both live");
        assert_eq!(bump.stats().allocated(), 10);
    });
    
    println!("{hello} is still live");
    assert_eq!(bump.stats().allocated(), 5);
});

assert_eq!(bump.stats().allocated(), 0);

// or you can use scope guards
{
    let mut guard = bump.scope_guard();
    let mut bump = guard.scope();

    let hello = bump.alloc_str("hello");
    assert_eq!(bump.stats().allocated(), 5);
    
    {
        let mut guard = bump.scope_guard();
        let bump = guard.scope();

        let world = bump.alloc_str("world");

        println!("{hello} and {world} are both live");
        assert_eq!(bump.stats().allocated(), 10);
    }
    
    println!("{hello} is still live");
    assert_eq!(bump.stats().allocated(), 5);
}

assert_eq!(bump.stats().allocated(), 0);

You can also use the unsafe checkpoint api to reset the bump pointer to a previous location.

let checkpoint = bump.checkpoint();

{
    let hello = bump.alloc_str("hello");
    assert_eq!(bump.stats().allocated(), 5);
}

unsafe { bump.reset_to(checkpoint); }
assert_eq!(bump.stats().allocated(), 0);

Allocator API

Bump and BumpScope implement allocator_api2::alloc::Allocator. With this you can bump allocate allocator_api2::boxed::Box, allocator_api2::vec::Vec and collections from other crates that support it like hashbrown::HashMap.

A bump allocator can grow, shrink and deallocate the most recent allocation. When bumping upwards it can even do so in place. Growing other allocations will require a new allocation and the old memory block becomes wasted space. Shrinking or deallocating other allocations does nothing which means wasted space.

A bump allocator does not require deallocate or shrink to free memory. After all, memory will be reclaimed when exiting a scope or calling reset. You can wrap a bump allocator in a type that makes deallocate and shrink a no-op using without_dealloc and without_shrink.

use bump_scope::Bump;
use allocator_api2::boxed::Box;
let bump: Bump = Bump::new();

let boxed = Box::new_in(5, &bump);
assert_eq!(bump.stats().allocated(), 4);
drop(boxed);
assert_eq!(bump.stats().allocated(), 0);

let boxed = Box::new_in(5, bump.without_dealloc());
assert_eq!(bump.stats().allocated(), 4);
drop(boxed);
assert_eq!(bump.stats().allocated(), 4);

Feature Flags

This crate supports no_std, unless the std feature is enabled.

  • std (default):

    Adds implementations of std::io traits for BumpBox and {Fixed, Mut}BumpVec. Activates alloc feature.

  • alloc (default):

    Adds implementations interacting with String and Cow<str>.

  • serde:

    Adds Serialize implementations for BumpBox, strings and vectors. Adds DeserializeSeed implementations for strings and vectors.

  • nightly-allocator-api (requires nightly):

    Enables allocator-api2's nightly feature which makes it reexport the nightly allocator api instead of its own implementation. With this you can bump allocate collections from the standard library.

  • nightly-coerce-unsized (requires nightly):

    Makes BumpBox<T> implement CoerceUnsized. With this BumpBox<[i32;3]> coerces to BumpBox<[i32]>, BumpBox<dyn Debug> and so on.

  • nightly-const-refs-to-static (requires nightly):

    Makes Bump::unallocated a const fn.

Bumping upwards or downwards?

Bump direction is controlled by the generic parameter const UP: bool. By default, UP is true, so the allocator bumps upwards.

  • Bumping upwards...
    • has the advantage that the most recent allocation can be grown and shrunk in place.
    • makes alloc_iter(_mut) and alloc_fmt(_mut) faster.
  • Bumping downwards...
    • uses slightly fewer instructions per allocation.
    • makes alloc_iter_mut_rev faster.

Minimum alignment?

The minimum alignment is controlled by the generic parameter const MIN_ALIGN: usize. By default, MIN_ALIGN is 1.

Changing the minimum alignment to e.g. 4 makes it so allocations with the alignment of 4 don't need to align the bump pointer anymore. This will penalize allocations of a smaller alignment as their size now needs to be rounded up the next multiple of 4.

This amounts to about 1 or 2 instructions per allocation.

GUARANTEED_ALLOCATED parameter?

When GUARANTEED_ALLOCATED is true, the bump allocator is in a guaranteed allocated state. That means that it must have already allocated a chunk from its backing allocator.

When GUARANTEED_ALLOCATED is false, the bump allocator may or may not have allocated chunks. You can create a bump allocator with no allocated chunks with Bump::unallocated.

You need a guaranteed allocated Bump(Scope) to create scopes via scoped and scope_guard. You can convert a maybe unallocated Bump(Scope) into a guaranteed allocated one with into_guaranteed_allocated or as_guaranteed_allocated(_mut).

The point of this is so Bumps can be created without allocating memory and even const constructed when the feature nightly-const-refs-to-static is enabled. At the same time Bump's that have already allocated a chunk don't suffer runtime checks for entering scopes and creating checkpoints.

License

Licensed under either of:

at your option.

Your 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.

Comments
  • A single lifetime parameter in `BumpVec`

    A single lifetime parameter in `BumpVec`

    I have not delved into it too deeply, but it seems like it should be possible to only have one lifetime parameter for BumpVec (and by extension BumpString). This is similar to bumpalo Vec. The lifetime is there to ensure that it either does not outlive the Bump or the BumpScopeGuard, depending on if the scope is present. It seems like a single lifetime could serve both purposes. However just changing BumpVec to this:

        fixed: FixedBumpVec<'a, T>,
        pub(crate) bump: &'a mut BumpScope<'a, A, MIN_ALIGN, UP>,
    

    Would make it invariant over 'a, which is probably not desirable.

    opened by CeleritasCelery 8
  • Feature request: const fn new

    Feature request: const fn new

    Making Bump::new be const fn allows for use in thread locals via

    thread_local! {
        static X: Bump = const { Bump::new() };
    }
    

    This makes thread local access much faster because we do not need to perform a dynamic check for each access to the thread local.

    enhancement 
    opened by kstrafe 2
  • `MutBumpVec` crashes when bump pointer is not aligned

    `MutBumpVec` crashes when bump pointer is not aligned

    This will crash:

    #[test]
    fn unaligned_mut_bump_vec() {
        let mut bump: Bump = Bump::new();
    
        bump.alloc(8u8);
    
        let mut vec = MutBumpVec::new_in(&mut bump);
        vec.push(32u32);
    
        let slice = vec.into_slice();
        dbg!(slice);
    }
    
    bug 
    opened by bluurryy 1
  • Allow `scoped`, `scope_guard` and such when `!GUARANTEED_ALLOCATED`

    Allow `scoped`, `scope_guard` and such when `!GUARANTEED_ALLOCATED`

    In the !GUARANTEED_ALLOCATED cases we just need to call ensure_allocated before doing the thing. The api friction of having to call as_guaranteed_allocated_mut is not justified. Its just a single branch.

    enhancement 
    opened by bluurryy 1
  • Rename `INIT` parameter

    Rename `INIT` parameter

    "INIT" is not a great name for what it represents.

    In the standard library "init" / "uninit" are used exclusively for talking about uninitialized memory. This is not what it means in bump-scope, so thats confusing.

    Maybe ALLOCATED, Bump::unallocated, into_allocated and as_allocated?

    Perhaps use a more descriptive name like MAYBE_UNALLOCATED which would be !INIT?

    enhancement 
    opened by bluurryy 1
  • Allocated ZSTs are immediately dropped

    Allocated ZSTs are immediately dropped

    ZSTs should not be dropped as we may depend on their side-effects. Bumpalo does not drop ZSTs. If we add data to the type it is also not dropped.

    use allocator_api2::alloc::Global;
    
    type Bump = bump_scope::Bump<Global, 1, false>;
    // type Bump = bumpalo::Bump;
    
    #[test]
    fn mytest() {
        struct DropEmit;
        impl Drop for DropEmit {
            fn drop(&mut self) {
                println!("=== DROPEMIT DROPPED === SHOULD NOT HAPPEN");
            }
        }
    
        let store = Bump::new();
        println!("Allocating");
        let dropemit = store.alloc(DropEmit);
        println!("Right after allocation");
        dropemit.into_raw();
    }
    
    bug 
    opened by kstrafe 1
  • `BumpVec::into_iter` should return `IntoIter<'a, T>` instead of `IntoIter<'b, T>`

    `BumpVec::into_iter` should return `IntoIter<'a, T>` instead of `IntoIter<'b, T>`

    This, along with the accompanying documentation was wrongly copy pasted from MutBumpVec.

    BumpVec should act like a FixedBumpVec with regards to IntoIter.

    enhancement 
    opened by bluurryy 0
  • Immutably borrowing `BumpVec`?

    Immutably borrowing `BumpVec`?

    We could implement a BumpVec that just immutably borrows the bump allocator. There is a stub implementation in src/vec.rs that we use for alloc_iter and alloc_fmt because the standard Vec produced really bad code for some reason.

    An immutably borrowing BumpVec is pretty much the same as Vec. But it could implement conversion to FixedBumpVec and BumpBox<[T]> and back. MIN_ALIGN would also affect it unlike Vec.

    If so we should probably rename the current BumpVec, MutBumpVec or something.

    opened by bluurryy 0
  • Add `FixedBumpVec::push_in`

    Add `FixedBumpVec::push_in`

    When using a mutable BumpScope you can't have collections referring to the BumpScope at the same time. So you can't have:

    struct Foo<'b, 'a> {
        bump: &'b mut BumpScope<'a>,
        vec: BumpVec<'b, 'a, u32>,
    }
    

    where vec is allocated in bump.

    But you can work around it by using FixedBumpVecs and turning them into a BumpVecs temporarily:

    struct Foo<'b, 'a> {
        bump: &'b mut BumpScope<'a>,
        fixed_vec: FixedBumpVec<'a, u32>,
    }
    
    impl<'b, 'a> Foo<'b, 'a> {
        fn push(&mut self, value: u32) {
            let Self { bump, fixed_vec } = self;
    
            let mut vec = core::mem::take(fixed_vec).into_vec(bump);
            vec.push(value);
    
            *fixed_vec = vec.into_fixed_vec();
        }
    }
    

    Let's add push_in(bump, value), so we can just write:

    impl<'b, 'a> Foo<'b, 'a> {
        fn push(&mut self, value: u32) {
            self.fixed_vec.push_in(self.bump, value);
        }
    }
    
    enhancement 
    opened by bluurryy 0
  • Missing `Vec`, `String`, `slice` api tracking issue

    Missing `Vec`, `String`, `slice` api tracking issue

    We are missing some api for BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec, MutBumpVecRev, BumpBox<str> and BumpString that exists in Vec, slice, String or str:

    • [ ] retain(_mut)
      • [x] BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec
      • [ ] BumpBox<str>, BumpString
      • [ ] MutBumpVecRev
    • [ ] drain
      • [x] BumpBox<[T]> FixedBumpVec, BumpVec, MutBumpVec
      • [ ] BumpBox<str>, BumpString
      • [ ] MutBumpVecRev
    • [ ] extract_if
      • [x] BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec
      • [ ] BumpBox<str>, BumpString
      • [ ] MutBumpVecRev
    • [x] extend_from_within
      • [x] BumpVec
      • [x] MutBumpVec
      • [x] MutBumpVecRev
      • [x] BumpString
    • [ ] into_flattened
      • [x] BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec
      • [ ] MutBumpVecRev
    • [ ] splice
      • [ ] BumpBox<[T]>
      • [ ] FixedBumpVec
      • [ ] BumpVec
      • [ ] MutBumpVec
      • [ ] MutBumpVecRev
    • [ ] dedup, dedup_by, dedup_by_key
      • [x] BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec
      • [ ] BumpVecRev
    • [ ] partition_dedup, partition_dedup_by, partition_dedup_by_key
      • [ ] BumpBox<[T]>, FixedBumpVec, BumpVec, MutBumpVec
      • [ ] BumpVecRev
    tracking issue low priority 
    opened by bluurryy 0
Owner
null
Encode and decode dynamically constructed values of arbitrary shapes to/from SCALE bytes

scale-value · This crate provides a Value type, which is a runtime representation that is compatible with scale_info::TypeDef. It somewhat analogous t

Parity Technologies 15 Jun 24, 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!

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
Blazingly fast interpolated LUT generator and applicator for arbitrary and popular color palettes.

lutgen-rs A blazingly fast interpolated LUT generator and applicator for arbitrary and popular color palettes. Theme any image to your dekstop colorsc

null 12 Jun 16, 2023
Fallible allocation support for Rust's Vec

Fallible allocation functions for Vec Fallible allocation functions for the Rust standard library's alloc::vec::Vec type. These functions are designed

Microsoft 4 Mar 14, 2023
🐴 RusTOTPony — CLI manager of one-time password generators aka Google Authenticator

?? RusTOTPony CLI manager of time-based one-time password generators. It is a desktop alternative for Google Authenticator. Installation Arch Linux Pa

German Lashevich 23 Jan 5, 2023
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

Antonio Sarosi 35 Dec 25, 2022
Traversal of tree-sitter Trees and any arbitrary tree with a TreeCursor-like interface

tree-sitter-traversal Traversal of tree-sitter Trees and any arbitrary tree with a TreeCursor-like interface. Using cursors, iteration over the tree c

Sebastian Mendez 12 Jan 8, 2023
a universal meta-transliterator that can decipher arbitrary encoding schemas, built in pure Rust

transliterati a universal meta-transliterator that can decipher arbitrary encoding schemas, built in pure Rust what does it do? You give it this: Барл

Catherine Koshka 7 Dec 21, 2022
Abuse the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code.

jscythe abuses the node.js inspector mechanism in order to force any node.js/electron/v8 based process to execute arbitrary javascript code, even if t

Simone Margaritelli 301 Jan 4, 2023
A PoC for the CVE-2022-44268 - ImageMagick arbitrary file read

CVE-2022-44268 Arbitrary File Read PoC - PNG generator This is a proof of concept of the ImageMagick bug discovered by https://www.metabaseq.com/image

Cristian 'void' Giustini 100 Feb 19, 2023
AI-TOML Workflow Specification (aiTWS), a comprehensive and flexible specification for defining arbitrary Ai centric workflows.

AI-TOML Workflow Specification (aiTWS) The AI-TOML Workflow Specification (aiTWS) is a flexible and extensible specification for defining arbitrary wo

ruv 20 Apr 8, 2023
Animated app icons in your Dock that can run an arbitrary shell script when clicked.

Live App Icon for Mac Animated app icons in your Dock that can run an arbitrary shell script when clicked. Requirements macOS 13 (Ventura) or higher X

Daichi Fujita 13 Jun 8, 2023
A library that allows for the arbitrary inspection and manipulation of the memory and code of a process on a Linux system.

raminspect raminspect is a crate that allows for the inspection and manipulation of the memory and code of a running process on a Linux system. It pro

Liam Germain 24 Sep 26, 2023
Eventually consistent values for Rust

Eventuals give you the most up-to-date snapshots of some value. They are like Futures that update over time, continually resolving to an eventually co

Edge & Node 110 Dec 31, 2022
Rust TUI library - Clipping region is a set of min/max x/y values applied to the existing region

TinyBit Clipping region is a set of min/max x/y values applied to the existing region A TUI lib This is not yet production ready T O D O TODO: bugs: T

Togglebit 13 May 3, 2022
Generic extensions for tapping values in Rust.

tap Suffix-Position Pipeline Behavior This crate provides extension methods on all types that allow transparent, temporary, inspection/mutation (tappi

Alexander Payne 213 Dec 30, 2022
Quickly save and retrieve values for shell scripts.

Quickly save and retrieve values for shell scripts.

Alex Andrade 2 Dec 15, 2022
a crate to swap values between possibly-overlapping references

omniswap: a crate to swap values between possibly-overlapping references Motivating Example You cannot simply use std::mem::swap to replace values wit

Masaki Hara 21 Nov 30, 2022