~hime/vm-arena

A simple arena allocator for Rust with virtual memory backing
42ffa5dc — Robert Straw 15 days ago
(docs) update changelog
1eab9f86 — Robert Straw 15 days ago
(bump) v0.2.1
ff8c2c18 — Robert Straw 15 days ago
(fb) do bounds check before pointer arithmetic

clone

read-only
https://git.sr.ht/~hime/vm-arena
read/write
git@git.sr.ht:~hime/vm-arena

You can also use your local clone with git send-email.

#vm-arena: these pages are made of dreams

This crate allows you to create a simple bump-allocated arena. It takes advantage of running on modern operating systems to implement its backing storage as a virtual memory allocation. As such, on supported platforms, the arena will not use physical pages until you issue the first write to them.

The allocator's API has been kept fairly simplistic and minimal. This is not meant to be a general-purpose allocator. It is meant for creating scratch space to bundle together allocations/deallocations for objects with similar lifetimes. This crate is designed to be easily ported to other platforms, including ones that do not expose the full Rust std library.

#TODO

  • There are tests, but this crate uses a lot of unsafe code and the soundness is far from proven. Most likely it will fall over if you are doing extremely large allocations, "weird" (negative/zero-sized) allocations, or allocations that exhaust or nearly exhaust the backing storage.

  • Reset currently does not zero memory between calls, theoretically this could be provided for "no cost" on platforms with real virtual memory by releasing and re-creating the reservation, however that would be quite expensive to provide in the physically-backed fallback allocator. This may change in the future if it causes me great pain. However please note that the current recycling behavior is guaranteed by our tests and API docs. If it is changed in the future it will constitute a breaking change.

  • Move the examples from this README into Doc-tests and expand on them.

  • Look into supporting allocator-api2 for people using stable Rust to give them a safe API.

#Disclaimer

This is alpha quality software. Any use is done at your own risk.

I urge you not to use this crate in security-sensitive contexts. By its nature it recycles memory, and all its memory is mapped R/W. It is fairly easy to do terrible things to your neighbors, or otherwise leak their secrets.

Also this is also most emphatically not meant to be a general purpose global allocator. The support for the allocator_api is mostly convenience for doing placement new inside of std::collections.

This crate is primarily meant for doing rapid allocation/deallocation of groups of objects with a fixed lifetime. For e.g. an arena for allocating objects that live for one frame of a video game, or allocating objects that live during a request-response cycle.

#Building

Include this in your Cargo.toml as follows:

# with nightly-only (!) allocator_api
# make sure to add `#![feature(allocator_api)] to your crate
vm-arena = { version = "0.1", features = ["allocator-api"] }

# with multi-threading
vm-arena = { version = "0.1", features = ["smp"] }

# force the fallback allocator even on supported platform(s)
vm-arena = { version = "0.1", features = ["fallback"] }

# without multi-threading
vm-arena = "0.1"

You may get a build-time error saying src/os.rs or src/os/mod.rs was not found. What that means is there was no implementation found matching both:

  • all the features you requested.
  • the target platform you are building for.

The os module does not exist at that path, and it never should be looking at that path. If the build is working correctly that module should always be loaded, via #[cfg] directives, at a different platform-specific path.

Most likely you will need to select a supported set of features, or port this library to your target platform.

#Features

  • Support for unix and windows

    • This crate will use libc to provide a backend on Unix-like operating systems, and the win32 API is used on the Windows operating system. These implementations will be automatically selected at compile time based on the target_family cfg-attribute.

    • These are full featured implementations which use virtual memory to allow users to reserve (frankly) ludicrously-sized arenas.

  • #![no_std] support via alloc

    • This crate can use alloc::Vec as backing storage to provide its bump allocator API over physically backed memory. Though this somewhat defeats the purpose of the crate, it does allow you to write code and run it unmodified in resource constrained environments.
  • smp: enables thread-safe implementations of the allocator

    • This somewhat defeats the purpose of using this crate, as a bump allocator is meant to be fast, and generally speaking the locks and atomics used to guard the allocator's state are "not fast™", but presumably you know what you're doing.

    • Also, the author is an idiot, so this implementation is the most likely to have bugs. As such I implore you to consider structuring your program so that they have thread-local arenas and do not need to share them across threads.

    • However, sometimes, you just wanna fucking Send it ... I get it.

#Usage

Get a default (extremely large, virtual) allocation:

// Allocate an extremely large (128GiB) virtual allocation
let mut arena = VmArena::default();

// Create a Vec<u8> inside that allocation
let mut v: Vec<u8, _> = Vec::with_capacity_in(4096, &arena);

// Use it as normal
v.push(1);
v.push(2);
v.push(3);

// NOTE: v must be dropped first, as its backing RawVec borrows the arena.
drop(v);
arena.reset();

Alternatively you can get a fixed-size allocation instead:

// Create a much smaller 4KiB (1 page) allocation
let mut arena = VmArena::with_capacity(1 * 4096);

// Create a Vec<u8> inside that allocation
let mut v: Vec<u8, _> = Vec::with_capacity_in(4096, &arena);

for i in 0..8192 {
    v.push((i % 64) as u8);
}

// NOTE: This example will compile, but lead to a runtime OOM panic!

#Thanks

This crate was largely made possible by the following resources:

  • I would like to thank both Shawn McGrath and Ryan Fleury. Their streams/videos served as inspiration for leaning on the OS virtual memory faculties, as well as for bundling lifetimes together into scoped arenas. (Ryan's Substack in particular has been a great resource for figuring this stuff out.) This is somewhat contrarian to how Rust typically does memory management, but it actually fits quite nicely with the borrow-checker.

  • The bumpalo crate is a much more feature-rich bump allocator. It also supports growable arenas without requiring OS specific features. The raison d'être for my crate is explicit support for fixed-sized arenas in resource constrained environments. If you do not need that I would strongly suggest you check out their crate instead!

  • The virt-arena crate is very similar in principle to this one. It was a great example of how to package this idea into a multi-platform crate. Their crate is much more likely to be correct (as it lacks the multithreading capabilities of this one), so if you do not need to move the allocator between threads or run in a no_std environment I would suggest you check out their crate instead!