arena #
Description
arena provides scoped, per-thread arena allocators. While an arena is pushed on a thread, every allocation that V code on that thread makes through V's allocation functions comes from the arena: strings, arrays, maps, string builders, &T{} values, including the ones made inside other vlib modules. The memory is released in bulk by reset() or free().
This gives long-running, multi-threaded programs built with -gc none bounded memory use, without a garbage collector:
import arena
import strings
fn main() {
mut a := arena.new()
defer {
a.free()
}
mut longest := ''
for i in 0 .. 1000 {
a.push()
mut sb := strings.new_builder(64)
for j in 0 .. i % 10 {
sb.write_string('${i}:${j} ')
}
line := sb.str()
a.pop()
if line.len > longest.len {
longest = line.clone() // copy the value out of the arena
}
a.reset() // all memory of the iteration is reused in the next one
}
println(longest)
}
Rules
push()makes an arena the allocator of the current thread,pop()restores the previous one. Pushes nest;pop()panics, when its arena is not the innermost active arena of the thread. Usedefer { a.pop() }to pop on every return path.- An arena can be pushed on one thread at a time. Spawned threads start without an active arena, so a scope never leaks into another thread by accident.
- Memory allocated in an arena stays valid after
pop(), untilreset()orfree(). Copy values that must outlive it with.clone()afterpop(). free()of arena memory (also by vlib code, likes.free()orarr.free()) does nothing. Growing arena memory (realloc) afterpop()copies it to the current allocator, so a map or array built in an arena can keep growing after the arena was popped.- Values stored into longer lived places while an arena is active (globals, caches, errors returned from the scope) point into the arena, and dangle after
reset()orfree(). Initialize lazily created global state before pushing an arena. - Growing an array, map or string builder allocates new storage, so one that was created before
push()and grows inside the scope gets storage in the arena, which dangles afterreset()too. Reserve its capacity beforepush(), or grow it afterpop(). - Closures, channels,
sharedvalues and other objects that are created inside a scope live in the arena as well. A thread that is spawned in the scope and still uses them afterreset()orfree()uses freed memory: wait for such threads before releasing the arena. reset()andfree()panic when the arena is still pushed. Using an arena afterfree()panics. Do not use a copy of anArenaafter the original was freed.- Pop every arena before its thread ends.
Supported builds
-gc none: the main use case.- The default Boehm GC: arena chunks are allocated as uncollectable, but scanned memory, so GC objects that are only referenced from arena memory stay alive.
-preallocand-gc vgcare not supported, and usingarenawith them is a compile time error.-preallochas its own scopes (prealloc_scope_begin()).
Programs that do not import arena are not affected: V compiles the allocator hooks into builtin (with -d builtin_arena) only for programs that import the module. Until such a program creates its first arena, the hooks cost one atomic check per allocation and free(). The first arena publishes all allocator hooks together, including when another thread is allocating.
fn active #
fn active() bool
active reports whether the current thread allocates from an arena.
fn new #
fn new(config Config) Arena
new creates an arena. It does not allocate any arena memory before its first use. Release it with free when it is no longer needed.
struct Arena #
struct Arena {
mut:
handle voidptr
}
Arena is a scoped, per-thread bump allocator. While it is pushed on a thread, every allocation that V code on that thread makes through V's allocation functions (strings, arrays, maps, string builders, &T{}, ...) comes from the arena. Its memory is released in bulk by reset or free. An Arena is a handle: do not use a copy of it after the original was freed.
fn (Arena) push #
fn (a &Arena) push()
push makes a the allocator of the current thread, until the matching pop. Pushes nest: the arena pushed last serves the allocations. An arena can be pushed on only one thread at a time. Spawned threads start without an active arena.
fn (Arena) pop #
fn (a &Arena) pop()
pop restores the allocator that was active before a was pushed. It panics, when a is not the innermost active arena of the current thread. Memory allocated in a stays valid until a.reset() or a.free(); use .clone() after pop to copy a value out of the arena.
fn (Arena) reset #
fn (mut a Arena) reset()
reset releases all memory allocated in a, so that the arena can be reused. It keeps one chunk, so an arena that is reset in a loop stops allocating once it has grown to the size the loop needs. It panics, when a is still pushed.
fn (Arena) free #
fn (mut a Arena) free()
free releases a and all memory allocated in it. It panics, when a is still pushed. Using a after free panics, while calling free again does nothing.
fn (Arena) used #
fn (a &Arena) used() isize
used returns the number of bytes allocated in a since it was created or last reset.
fn (Arena) capacity #
fn (a &Arena) capacity() isize
capacity returns the number of bytes in the chunks that a holds.
fn (Arena) owns #
fn (a &Arena) owns(ptr voidptr) bool
owns reports whether ptr points into memory allocated in a.
fn (Arena) is_current #
fn (a &Arena) is_current() bool
is_current reports whether a is the innermost active arena of the current thread, i.e. whether it serves the allocations of the thread right now.
struct Config #
struct Config {
pub:
// chunk_size is the size of the first chunk in bytes. Later chunks double
// in size, up to 4MB (or `chunk_size`, when it is bigger).
chunk_size int = 64 * 1024
}
Config holds the options for new.