arena

package module
v0.0.0-...-4c8fb04 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2026 License: MIT Imports: 3 Imported by: 0

README

Go Arena: High-Performance Zero-GC Allocator

English | Русский 🇷🇺

Go Version Go Report Card

Go Arena is a memory allocator for Go designed for systems with extreme latency requirements (C10M, HFT, GameDev).

The idea is simple: instead of millions of tiny heap allocations and GC pauses, we use a bump-pointer arena that resets in O(1). The garbage collector stops seeing the "small trash," and latency becomes flat.

Benchmarks

Comparisons performed on Intel Core i5-12450H (Windows/amd64). Lower is better.

1. Stress Test (Real World Scenario)

Simulation of a loaded HTTP server (log parsing, DTO creation).

  • Load: 1,000,000 requests.
  • Concurrency: 100 workers.
  • Heap Pressure: 5 million live objects in the background.
Metric Standard Heap Go Arena Result
Throughput 53,251 RPS 104,454 RPS 2x faster
Max Latency 378 ms 72 ms 5x more stable
p99 Latency 5.6 ms 2.5 ms Lower tail latency
GC Pause Stop-The-World Zero No GC pressure
2. Performance Benchmarks (1,000,000 ops)
Operation (1M ops) Standard Go (Runtime) Go Arena Speedup Allocs/Op (Arena)
Struct Allocation 35.3 ns/op 7.5 ns/op ~4.7x 0
String Copy 17.1 ns/op 6.5 ns/op ~2.6x 0
Make Slice (1M len) 4,467,774 ns/op 7.4 ns/op ~600,000x* 0

Note: MakeSlice in Arena is O(1) (pointer bump), while Runtime make is O(N) due to memory zeroing. This provides massive performance gains for large temporary buffers.

Full raw benchmark output
BenchmarkNewObject/Runtime/1000000-12         34          35328644 ns/op        64000062 B/op    1000000 allocs/op
BenchmarkNewObject/Arena/1000000-12          138           7549980 ns/op               0 B/op          0 allocs/op
BenchmarkAllocString/Runtime/1000000-12       78          17134946 ns/op        32000003 B/op    1000000 allocs/op
BenchmarkAllocString/Arena/1000000-12        202           6577214 ns/op               0 B/op          0 allocs/op
BenchmarkMakeSlice/Runtime/1000000-12        241           4467774 ns/op        56000512 B/op          1 allocs/op
BenchmarkMakeSlice/Arena/1000000-12      157475995                7.408 ns/op           0 B/op          0 allocs/op
3. Competitor Benchmarks (pooling alternatives)

Also includes comparisons with:

  • sync.Pool for object reuse.
  • github.com/valyala/bytebufferpool for byte buffer reuse.

Run smoke benchmarks:

go test -run ^$ -bench 'Benchmark(AllocationCompetitors|BufferCompetitors|ArenaPoolThroughput|ArenaPoolVsRaw)' -benchtime=200ms

Installation

go get github.com/VoolFI71/go-arena

Examples

1. HTTP / TCP server (Arena Pool pattern)

This is the primary scenario. Use ArenaPool to reuse memory between requests.

package main

import (
    "fmt"
    "github.com/VoolFI71/go-arena"
)

// Global pool. 64KB is enough for most HTTP requests.
var pool = arena.NewArenaPool(64*1024, 0)

func HandleLog(ctx *fasthttp.RequestCtx) {
    // 1. Get an arena from the pool
    mem := pool.Get()
    defer pool.Put(mem)

    // 2. Allocate a struct inside the arena
    u := arena.New[UserData](mem)

    // 3. Strings without heap allocations
    u.Name = mem.AllocBytesToString(ctx.PostBody())

    ctx.SetStatusCode(200)
}
2. Slices (MakeSlice + Append)

Shows the correct append replacement that requires the arena argument.

// 4. Work with dynamic data
func DynamicWork(mem *arena.Arena) {
    // Create an empty slice in the arena
    items := arena.MakeSlice[int](mem, 0, 10)

    // Use arena.Append instead of built-in append
    for i := 0; i < 100; i++ {
        items = arena.Append(mem, items, i)
    }
    // Even if the slice grows 10x, allocations stay inside the arena
}
3. Reset in a loop

Useful for long loops in a single goroutine without a pool.

func Worker() {
    mem := arena.NewArena(1024*1024, 0) // 1MB arena

    for i := 0; i < 1000; i++ {
        processIteration(mem)
        mem.Reset() // Clear everything from this iteration in O(1)
    }
}

The Safety Contract

Manual memory management requires discipline. In short:

  • Scope Limit: do not return pointers to arena objects outside their lifetime (after pool.Put or Reset).
  • Concurrency: Arena is not thread-safe. Use ArenaPool for parallel use.
  • GC Blindness (Dangling Pointers): Arena memory hides its contents from the Go Garbage Collector. Never store pointers to heap-allocated objects inside arena-allocated structures. The GC will not see the reference and may prematurely free the heap object, causing crashes.
  • Dirty Memory (Security): Reset() is O(1) and does NOT zero out memory. Calling MakeSlice[byte] returns bytes containing residual data from previous requests. Be sure to overwrite the slice completely before returning it to the user to prevent data leaks (e.g. passwords, PII from previous clients).
  • Data Independence: if you need long-lived data, make a physical copy (e.g., serialize).
Anti-patterns (do not do this)
// ERROR: Returning a pointer to arena memory
func BadFunction() *User {
    mem := pool.Get()
    defer pool.Put(mem) // Memory is cleared here!

    u := arena.New[User](mem)
    return u // <--- DANGER! Caller gets a dangling pointer.
}

// ERROR: Using one arena from multiple goroutines
func AsyncLogic(mem *arena.Arena) {
    go func() {
        // Arena is not thread-safe (except Pool).
        // This will cause a data race.
        _ = arena.New[int](mem)
    }()
}

// ERROR: GC Blindness (Storing heap pointers in arena)
type Node struct {
    Data *string
}
func GCLeak(mem *arena.Arena) {
    node := arena.New[Node](mem)
    str := "Heap Allocated String"
    node.Data = &str // The GC will NOT trace this reference! 'str' will be freed.
}
Correct approach
// If you need to return data, serialize it
func GoodFunction() []byte {
    mem := pool.Get()
    defer pool.Put(mem)

    u := arena.New[User](mem)
    // ... fill u ...

    return json.Marshal(u) // Return a copy independent from the arena
}

API Reference

Allocators
  • New[T](a *Arena) *T — allocates an object of type T in the arena.
  • MakeSlice[T](a *Arena, len, cap int) []T — creates a slice.
  • AllocString(s string) string — copies a string/bytes into the arena.
  • AllocBytesToString(b []byte) string — copies []byte into the arena and returns string.
Helper functions
  • Append(a *Arena, slice []T, items ...T) []T — append equivalent that stays inside the arena.
Memory management
  • NewArenaPool(chunkSize, maxRetained int) *ArenaPool — thread-safe pool (recommended).
  • Reset() — instant arena cleanup (cursor -> 0).

API Stability and SemVer

  • Current stability level: v0 (pre-1.0). Breaking changes are still possible.
  • SemVer policy:
    • Patch: bug fixes and non-breaking internals.
    • Minor: new API additions without breaking existing signatures.
    • Major (or pre-1.0 breaking release): incompatible API changes.
  • Public API signatures are compile-checked in api_contract_test.go to catch accidental breaking changes.

Under the hood (Architecture)

Go Arena uses a linked list of memory chunks ([][]byte).

  • On creation, you get a single chunk (e.g., 64KB).
  • New[T] advances the allocation cursor inside the current chunk.
  • When the chunk is full, a new chunk is allocated and used.
  • Reset does not free chunks, it just resets indices for instant reuse.

Tip: choose chunkSize in ArenaPool so it covers ~90% of typical requests. This minimizes new chunk allocations.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append[T any](a *Arena, slice []T, items ...T) []T

Append adds items, allocating inside arena when cap is insufficient. / Append добавляет элементы в слайс, выделяя память в арене при нехватке cap.

func MakeSlice

func MakeSlice[T any](a *Arena, length int, capacity int) []T

MakeSlice creates a slice in the arena with given length and capacity. / MakeSlice создает слайс в арене с заданной длиной и емкостью.

func New

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

New allocates object of type T inside arena. / New выделяет объект типа T внутри арены.

Types

type Arena

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

Arena holds memory chunks and allocation cursor. / Arena хранит набор чанков памяти и курсор выделения.

Field order is intentional: the three hot fields (offset, curStart, curEnd) are placed first in the struct body so they occupy the very start of the first active cache line (right after the leading false-sharing guard). Cold fields (chunks, chunkSize, maxRetain) follow and may spill to CL2, but they are only touched during chunk growth and Reset — not on every alloc.

func NewArena

func NewArena(size int, maxRetained int) *Arena

NewArena creates an arena with fixed chunk size. / NewArena создает арену фиксированного размера чанка.

func (*Arena) AllocBytes

func (a *Arena) AllocBytes(n int) []byte

AllocBytes reserves n bytes in the arena and returns them as a []byte. The returned slice is valid until the next Reset or pool.Put call.

WARNING: Reset does NOT zero memory. The slice may contain residual data from previous allocations. Zero it yourself if the content is security-sensitive (e.g. passwords, PII) before passing it outside the request scope.

Panics if n < 0. Returns nil for n == 0.

func (*Arena) AllocBytesToString

func (a *Arena) AllocBytesToString(b []byte) string

AllocBytesToString copies []byte into arena and returns string. / AllocBytesToString копирует []byte в арену и возвращает как string.

func (*Arena) AllocString

func (a *Arena) AllocString(s string) string

AllocString copies string bytes into arena. / AllocString копирует байты строки внутрь арены.

func (*Arena) Reset

func (a *Arena) Reset()

Reset resets cursors and trims memory by limit. / Reset сбрасывает курсоры и подрезает память по лимиту.

func (*Arena) UsedBytes

func (a *Arena) UsedBytes() int

type ArenaPool

type ArenaPool struct {
	Metrics PoolMetrics
	// contains filtered or unexported fields
}

ArenaPool stores and reuses arenas. / ArenaPool хранит и переиспользует арены.

func NewArenaPool

func NewArenaPool(chunkSize int, maxRetained int) *ArenaPool

NewArenaPool creates an arena pool. / NewArenaPool создает пул арен.

func (*ArenaPool) Get

func (p *ArenaPool) Get() *Arena

Get returns an arena from the pool. / Get возвращает арену из пула.

func (*ArenaPool) MetricsSnapshot

func (p *ArenaPool) MetricsSnapshot() PoolMetricsSnapshot

MetricsSnapshot returns a copy of current metrics values (atomic-safe read). MetricsSnapshot возвращает копию текущих значений метрик (без гонок и блокировок).

func (*ArenaPool) Put

func (p *ArenaPool) Put(a *Arena)

Put returns an arena to the pool. / Put возвращает арену в пул.

type PoolMetrics

type PoolMetrics struct {
	TotalCapacityBytes atomic.Uint64

	ActiveArenas atomic.Int64

	GetCount atomic.Uint64

	TotalUsedBytes atomic.Uint64
	// contains filtered or unexported fields
}

type PoolMetricsSnapshot

type PoolMetricsSnapshot struct {
	TotalCapacityBytes uint64
	ActiveArenas       int64
	GetCount           uint64
	TotalUsedBytes     uint64
}

PoolMetricsSnapshot is a plain copy of metrics values. / PoolMetricsSnapshot — снепшот значений метрик.

Jump to

Keyboard shortcuts

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