arena

package module
v0.0.0-...-5fb9b8b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: Apache-2.0 Imports: 8 Imported by: 1

README

Arena Memory Allocator

Go Reference

WARNING: Experimental — This project is in early development. The API is not stabilized and may change without notice. Do not use in production.

A high-performance memory allocator for Go that reduces garbage collection (GC) overhead by managing object lifetimes explicitly.

Features

  • Reduces GC Pressure: Allocates objects in contiguous chunks, minimizing GC scans.
  • Zero-Allocation APIs: Methods like Malloc, New, and NewSlice avoid heap allocations.
  • Thread Safety: Optional spinlock-based synchronization.
  • Customizable: Configure chunk sizes, memory sources, and pooling behavior.
  • Arena-Native Containers: Map[K,V] and Vector[T] with all data stored in arena memory.
  • Type Safety: Compile-time type validation rejects unsupported types (map, chan, func).

Installation

go get github.com/limpo1989/arena

Quick Start

package main

import (
	"fmt"

	"github.com/limpo1989/arena"
)

func main() {
	ar := arena.NewArena()
	defer ar.Reset()

	// Allocate primitives
	num := arena.New[int](ar)
	*num = 42
	fmt.Println("print num:", *num)

	// Allocate slices
	slice := arena.NewSlice[string](ar, 0, 10)
	slice = arena.Append(ar, slice, "hello", "world")
	fmt.Println("print slice:", slice)

	// Use vectors
	vec := arena.NewVector[int](ar, 8)
	vec.Append(1, 2, 3)
	fmt.Println("print vec:", vec.At(0), vec.At(1), vec.At(2))

	// Use maps
	m := arena.NewMap[string, int](ar, 8)
	m.Put("answer", 42)
	if v, ok := m.Get("answer"); ok {
		fmt.Println("print map:", v)
	}
}

Memory Safety Rules

Arena memory is allocated as raw []byte slices. Go GC does not scan arena memory contents for pointers. The following rules govern the boundary between Go heap and arena memory.

WARNING: Never assign heap pointers to arena object fields

Arena object fields must be created through DeepCopy or arena APIs. Direct assignment may write a heap pointer into arena memory. Since GC cannot trace this reference, the heap target may be collected, producing a dangling pointer. This issue does not fail immediately — it causes random crashes at runtime and is extremely difficult to diagnose.

❌ p.Field = &heapObj          // DANGEROUS: arena holds a heap pointer
❌ p.Field = heapSlice         // DANGEROUS: backing array is on heap
❌ p.Field = &SomeType{}       // DANGEROUS: implicit heap allocation

✓ p.Field = DeepCopy(ar, val) // SAFE: value is copied into arena
✓ p.Field = ar.NewInt(42)     // SAFE: created via arena API
✓ p.Field = New[T](ar)        // SAFE: allocated in arena
Rule 1: Pointers flow from Heap into Arena, never the reverse
              Pointer direction
  ┌───────┐   ────────▶   ┌───────┐   ────────▶   ┌───────┐
  │       │    SAFE ✓     │       │    SAFE ✓     │       │
  │ Heap  │──────────────▶│ Arena │──────────────▶│ Arena │
  │       │               │       │               │       │
  └───────┘               └───────┘               └───────┘
      ▲                       │
      └────── UNSAFE ✗ ───────┘
          Arena → Heap pointer is invisible to GC
Rule 2: Arena objects must be self-contained

All data reachable from an arena object must also reside in arena memory. Use DeepCopy to copy values into arena instead of assigning references.

  Arena Memory (self-contained)
  ┌─────────────────────────────────────────────┐
  │  GameData struct                            │
  │  ┌───────────────────────────────────────┐  │
  │  │ Name string ──────▶ bytes in arena    │  │
  │  │ Tags  *Map ───────▶ Map in arena      │  │
  │  │ Items *Vector ────▶ Vector in arena   │  │
  │  └───────────────────────────────────────┘  │
  │         All references point inward          │
  └─────────────────────────────────────────────┘
Rule 3: Arena struct and management data stay on Go heap

The Arena object, chunkBlock structs, and internal maps are always on the Go heap. This is necessary — they hold []byte references to arena chunks, which is how GC keeps chunk data alive.

  Go Heap (GC-managed)
  ┌──────────────────────────────┐
  │ Arena struct                 │
  │  chunkBlocks ──▶ []byte refs │──▶ chunk data
  │  freelist    ──▶ []byte refs │
  └──────────────────────────────┘
       ▲              ▲
       │              │
  Map.allocator  Vector.allocator   ← arena→heap refs, safe because
                                        Arena always outlives its containers
Rule 4: Arena lifetime governs all pointers

All pointers into arena memory become invalid after Arena.Reset(). Using them after Reset is undefined behavior.

  NewArena()              Use phase                 Reset()
     │                       │                        │
     ▼                       ▼                        ▼
  ┌──────┐             ┌──────────┐            ┌──────────────┐
  │ Init │──▶ ... ──── │ Normal   │──▶ ... ──▶ │ All pointers │
  │      │             │ usage    │            │ invalidated  │
  └──────┘             └──────────┘            └──────────────┘
Supported and Rejected Types
Type Status Notes
bool, int*, uint*, float* ✓ Supported Value types, direct copy
string ✓ Supported Bytes copied into arena
*T (pointer) ✓ Supported Deep-copied recursively
[]T (slice) ✓ Supported Backing array in arena
[N]T (array) ✓ Supported Elements deep-copied
struct { ... } ✓ Supported Fields validated recursively
*arena.Map[K,V] ✓ Supported Arena-native hash map
*arena.Vector[T] ✓ Supported Arena-native dynamic array
map[K]V ✗ Rejected Use arena.Map[K,V]
chan T ✗ Rejected Not arena-compatible
func(...) ✗ Rejected Not arena-compatible
sync.Mutex ✗ Rejected Not arena-compatible

Type validation runs automatically at API entry points (New, DeepCopy, NewSlice, NewMap, NewVector) with clear error messages. Use arena.Validate[T]() for explicit checking.

API

Core Allocation
ar := arena.NewArena(
    arena.WithChunkSize(4096),
    arena.WithPoolSize(128),
    arena.WithEnableLock(true),
)

p := arena.New[MyStruct](ar)       // Allocate a struct
s := arena.NewSlice[int](ar, 0, 8) // Allocate a slice
s = arena.Append(ar, s, 1, 2, 3)  // Append to arena slice

cp := arena.DeepCopy(ar, original) // Deep copy into arena

ar.Free(p)  // Free individual allocation
ar.Reset()  // Free all allocations at once
Map[K,V]
m := arena.NewMap[string, int](ar, 16)
m.Put("key", 42)
v, ok := m.Get("key") 
m.Remove("key")
m.Len()
for k, v := range m.All() { /* ... */ }
m.Clear()
Vector[T]
v := arena.NewVector[int](ar, 8)
v.Append(1, 2, 3)
v.At(0)           // 1
v.Remove(2)       // remove by value
v.RemoveIdx(0)    // remove by index
v.Index(3)        // find by value, returns -1 if not found
v.Len(), v.Cap()
for i, val := range v.All() { /* ... */ }
v.Clear()

Performance

Arena reduces GC pauses by:

  1. Bulk Allocation: Objects are grouped in chunks, decreasing GC scan count.
  2. Lifetime Control: Allocations are freed together via Reset().
  3. Reduced Fragmentation: Chunk reuse minimizes heap fragmentation.
Benchmark (vs. Go heap)

Tested on Apple M4 Pro, allocating 1M largeMessage objects (40+ fields each):

Metric Go Heap Arena Improvement
GC Scan Time 25.6ms 2.7ms 9.4x faster
Living Objects 2,500,702 2,288 1,093x fewer

License

The arena is released under version 2.0 of the Apache License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append[T any](ar *Arena, src []T, values ...T) []T

Append extends a slice managed by the Arena, reallocating if necessary. The underlying memory is managed by the Arena.

func DeepCopy

func DeepCopy[T any](ar *Arena, v T) *T

DeepCopy allocates an object of type T from the Arena and performs a deep copy of the source object v into it. The underlying memory is managed by the Arena.

func MustValidate

func MustValidate[T any]()

func New

func New[T any](ar *Arena) *T

New allocates memory for a type T and returns a pointer to it. The object's lifetime is tied to the Arena.

func NewSlice

func NewSlice[T any](ar *Arena, length, capacity int) (result []T)

NewSlice allocates a slice of type T with the specified length and capacity. The underlying memory is managed by the Arena.

func Validate

func Validate[T any]() error

Types

type Arena

type Arena struct {
	// contains filtered or unexported fields
}

Arena manages memory chunks and provides allocation services with reduced GC overhead. It maintains reusable memory blocks and handles alignment automatically.

Locking: the internal lock (if enabled via WithEnableLock) protects only the allocator's chunk/freelist/refcount state. Map and Vector containers are NOT protected by this lock — concurrent access to individual container instances requires external synchronization.

func NewArena

func NewArena(ops ...Option) *Arena

NewArena creates a new Arena instance with customizable options. Options can configure chunk size, memory pool size, locking behavior, and memory source.

IMPORTANT: The caller must keep the Arena reachable by the GC (e.g., hold a reference in a non-arena variable) for the entire lifetime of any objects allocated from it. Arena-allocated objects store their *Arena reference inside arena memory (backed by []byte), which the GC does not scan. If the Arena becomes unreachable, the GC may collect it while arena objects are still in use, causing undefined behavior.

func (*Arena) AuditPointers

func (ar *Arena) AuditPointers(obj any) []PointerViolation

AuditPointers recursively scans an arena-allocated object for pointers that do not belong to this arena's memory. Returns a list of violations describing each non-arena pointer found, including the field path and type information.

This is a debugging tool — it walks the object graph using reflect and checks every pointer, slice backing array, string data, and func closure pointer against the arena's managed address ranges.

func (*Arena) Bytes

func (ar *Arena) Bytes(v []byte) (b []byte)

Bytes allocates a byte slice's backing array in the Arena and copies the input data. The slice header is returned by value (on stack), not allocated in arena. Only the backing data is arena-managed.

func (*Arena) Free

func (ar *Arena) Free(ptrT any)

Free releases a previously allocated memory block. The pointer must belong to this Arena.

func (*Arena) IsManagedPointer

func (ar *Arena) IsManagedPointer(ptr unsafe.Pointer) bool

IsManagedPointer reports whether the given pointer points into memory managed by this Arena.

func (*Arena) Malloc

func (ar *Arena) Malloc(sz uintptr) unsafe.Pointer

Malloc allocates a memory block of the given size. Returned pointer is aligned. Panics if size is zero.

func (*Arena) NewBool

func (ar *Arena) NewBool(v bool) *bool

NewBool allocates a boolean in the Arena and initializes it with the given value.

func (*Arena) NewFloat32

func (ar *Arena) NewFloat32(v float32) *float32

NewFloat32 allocates an float32 in the Arena and initializes it with the given value.

func (*Arena) NewFloat64

func (ar *Arena) NewFloat64(v float64) *float64

NewFloat64 allocates an float64 in the Arena and initializes it with the given value.

func (*Arena) NewInt

func (ar *Arena) NewInt(v int) *int

NewInt allocates an integer in the Arena and initializes it with the given value.

func (*Arena) NewInt8

func (ar *Arena) NewInt8(v int8) *int8

NewInt8 allocates an int8 in the Arena and initializes it with the given value.

func (*Arena) NewInt16

func (ar *Arena) NewInt16(v int16) *int16

NewInt16 allocates an int16 in the Arena and initializes it with the given value.

func (*Arena) NewInt32

func (ar *Arena) NewInt32(v int32) *int32

NewInt32 allocates an int32 in the Arena and initializes it with the given value.

func (*Arena) NewInt64

func (ar *Arena) NewInt64(v int64) *int64

NewInt64 allocates an int64 in the Arena and initializes it with the given value.

func (*Arena) NewString

func (ar *Arena) NewString(v string) (s *string)

NewString allocates a string header and data in the Arena, returning *string.

func (*Arena) NewUint

func (ar *Arena) NewUint(v uint) *uint

NewUint allocates an uint in the Arena and initializes it with the given value.

func (*Arena) NewUint8

func (ar *Arena) NewUint8(v uint8) *uint8

NewUint8 allocates an uint8 in the Arena and initializes it with the given value.

func (*Arena) NewUint16

func (ar *Arena) NewUint16(v uint16) *uint16

NewUint16 allocates an uint16 in the Arena and initializes it with the given value.

func (*Arena) NewUint32

func (ar *Arena) NewUint32(v uint32) *uint32

NewUint32 allocates an uint32 in the Arena and initializes it with the given value.

func (*Arena) NewUint64

func (ar *Arena) NewUint64(v uint64) *uint64

NewUint64 allocates an uint64 in the Arena and initializes it with the given value.

func (*Arena) Reset

func (ar *Arena) Reset()

Reset clears all allocated chunks and resets the Arena to its initial state. Existing pointers become invalid after this operation.

func (*Arena) String

func (ar *Arena) String(s string) string

String copies a string's data pointers into the arena and returns a string whose header can be inlined in a struct field. Only the string data is allocated in arena — no separate header allocation — so there is no orphaned pointer. Use NewString if you need *string.

type Map

type Map[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Map is an arena-native hash map using linear probing. All data (ctrl, keys, values) is stored in arena-allocated slices.

Map is NOT thread-safe. Concurrent calls to Put/Get/Remove on the same Map instance require external synchronization (e.g., sync.Mutex). The Arena's internal lock (if enabled) only protects the allocator, not the Map's internal state.

func NewMap

func NewMap[K comparable, V any](allocator *Arena, capacity int) *Map[K, V]

NewMap creates a new arena-native hash map with specified initial capacity. The returned Map is NOT thread-safe — use external synchronization (e.g., sync.Mutex) for concurrent access.

func (*Map[K, V]) AddIfAbsent

func (m *Map[K, V]) AddIfAbsent(key K, value V) bool

AddIfAbsent stores a key-value pair only if the key doesn't exist. Returns true if added, false if the key already existed.

func (*Map[K, V]) All

func (m *Map[K, V]) All() iter.Seq2[K, V]

All provides an iterator compatible with range loops.

func (*Map[K, V]) Clear

func (m *Map[K, V]) Clear()

Clear removes all entries and frees all value arena memory.

func (*Map[K, V]) Get

func (m *Map[K, V]) Get(key K) (V, bool)

Get retrieves the value for the given key. Returns the value and true if found, or the zero value and false otherwise.

func (*Map[K, V]) Len

func (m *Map[K, V]) Len() int

Len returns the number of entries in the map.

func (*Map[K, V]) MarshalJSON

func (m *Map[K, V]) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface. It serializes the Map as a JSON object ({"key": value, ...}).

func (*Map[K, V]) Put

func (m *Map[K, V]) Put(key K, value V)

Put stores a key-value pair. Existing values are freed and overwritten.

func (*Map[K, V]) Remove

func (m *Map[K, V]) Remove(key K)

Remove deletes a key-value pair and frees the value's arena memory.

func (*Map[K, V]) UnmarshalJSON

func (m *Map[K, V]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface. It clears the Map and populates it from a JSON object. All data is deep-copied into arena memory.

type Memory

type Memory interface {
	Alloc(size uintptr) []byte
	Free(m []byte)
}

Memory represents a alternative memory sources (e.g., mmap, cgo, shm).

type Option

type Option func(*arenaOptions)

Option defines a function type for configuring Arena parameters

func WithChunkSize

func WithChunkSize(chunkSize uintptr) Option

WithChunkSize sets the base allocation size for memory chunks. Larger values reduce allocation frequency but may increase waste. Minimum size is automatically aligned to system pointer size.

func WithEnableLock

func WithEnableLock(enableLock bool) Option

WithEnableLock enables thread-safe operation using a spinlock. Required when the Arena is accessed concurrently. When enabled, adds ~5-15ns overhead per allocation.

NOTE: This lock protects ONLY the Arena allocator's internal state (chunk management, freelist, refcounts). It does NOT protect the contents of Map or Vector containers allocated from this Arena. Concurrent access to individual Map/Vector instances requires external synchronization (e.g., sync.Mutex).

func WithMemory

func WithMemory(memory Memory) Option

WithMemory specifies a custom memory allocator implementing the Memory interface. Allows integration with alternative memory sources (e.g., mmap, cgo, shm). Default: heapMemory (standard Go allocations).

func WithPoolSize

func WithPoolSize(poolSize int) Option

WithPoolSize configures the maximum number of reusable chunks retained in the free list. Higher values improve reuse at the cost of increased memory retention.

type PointerViolation

type PointerViolation struct {
	Path    string        // Full field path, e.g. "GameState.Players.vec[3].Name"
	Kind    ViolationKind // Type of violation: pointer, slice, string, or func
	Address uintptr       // The offending pointer address
	Type    reflect.Type  // The reflect.Type of the field
	Hint    string        // Additional context to help diagnose the violation
}

PointerViolation describes a single pointer safety violation found in an arena object.

type Vector

type Vector[T any] struct {
	// contains filtered or unexported fields
}

Vector is an Arena-backed dynamic array providing type-safe operations. It reduces GC pressure by storing elements in contiguous Arena memory.

Vector is NOT thread-safe. Concurrent calls on the same Vector instance require external synchronization (e.g., sync.Mutex). The Arena's internal lock (if enabled) only protects the allocator, not the Vector's internal state.

func NewVector

func NewVector[T any](allocator *Arena, capacity int) *Vector[T]

NewVector creates a new Vector with specified initial capacity. The vector's memory is managed by the provided Arena allocator. The returned Vector is NOT thread-safe — use external synchronization (e.g., sync.Mutex) for concurrent access.

func (*Vector[T]) AddIfAbsent

func (v *Vector[T]) AddIfAbsent(value T) bool

AddIfAbsent adds an element only if it doesn't already exist in the vector.

func (*Vector[T]) All

func (v *Vector[T]) All() iter.Seq2[int, T]

All provides an iterator function compatible with range loops.

func (*Vector[T]) Append

func (v *Vector[T]) Append(values ...T) *Vector[T]

Append adds elements to the end of the vector.

func (*Vector[T]) At

func (v *Vector[T]) At(index int) T

At retrieves the element at the specified index.

func (*Vector[T]) Cap

func (v *Vector[T]) Cap() int

Cap returns the current capacity of the vector.

func (*Vector[T]) Clear

func (v *Vector[T]) Clear()

Clear remove all elements.

func (*Vector[T]) Equatable

func (v *Vector[T]) Equatable(equatable func(a, b T) bool) *Vector[T]

Equatable sets a custom equality comparison function for element comparison.

func (*Vector[T]) Index

func (v *Vector[T]) Index(value T) int

Index finds the first occurrence of an element. Index of first match, or -1 if not found

func (*Vector[T]) LastIndex

func (v *Vector[T]) LastIndex(value T) int

LastIndex finds the last occurrence of an element. Index of last match, or -1 if not found

func (*Vector[T]) Len

func (v *Vector[T]) Len() int

Len returns the current number of elements in the vector.

func (*Vector[T]) MarshalJSON

func (v *Vector[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface. It serializes the Vector as a JSON array ([elem1, elem2, ...]).

func (*Vector[T]) Remove

func (v *Vector[T]) Remove(value T) bool

Remove deletes the first occurrence of the specified element.

func (*Vector[T]) RemoveBy

func (v *Vector[T]) RemoveBy(limit int, fn func(index int, v T) bool) int

RemoveBy removes elements matching a condition with quantity control. use limit param to control maximum number of elements to remove (0 = unlimited)

func (*Vector[T]) RemoveIdx

func (v *Vector[T]) RemoveIdx(idx int)

RemoveIdx removes the element at the specified index.

func (*Vector[T]) UnmarshalJSON

func (v *Vector[T]) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface. It clears the Vector and populates it from a JSON array. All data is deep-copied into arena memory.

type ViolationKind

type ViolationKind string

ViolationKind describes the type of pointer violation found by AuditPointers.

const (
	ViolationPointer ViolationKind = "pointer"
	ViolationSlice   ViolationKind = "slice"
	ViolationString  ViolationKind = "string"
	ViolationFunc    ViolationKind = "func"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL