Skip to main content

Command Palette

Search for a command to run...

Memory Arenas in Golang

Published
3 min read
Memory Arenas in Golang
A

Curious about Outages, Distributed Systems, DevOps, Backend, SRE related stuffs.

Golang periodically runs a garbage-collection algorithm to free up unreachable objects. For longer programs, this task itself eats up a lot of CPU. Along with this Go runtime delays garbage collection as long as possible to free as much as possible memory in a single run.

To tackle this issue, Go 1.20 release added the arenas package. It helps to reduce the garbage collection overhead as well as runs the program faster. For this, Memory arenas allow allocating objects from contiguous regions of memory and free them all at once. Memory arenas can be used to allocate a large number of objects in a function, use them for a while, then release them all at once.

This is still an experimental feature, the API implementation for this is also unsupported, it is running behind the GOEXPERIMENT=arenas environment variable.

Let's start with a basic example

import "arena"

type T struct {
    Foo string
    Bar [16]byte
}

func processRequest(req *http.Request) {
    // creating an arena at the beginning
    mem := arena.NewArena()
    free the arena at the end
    defer mem.Free()

    // allocate some objects from the arena
    for i := 0; i < 10; i++ {
        obj := arena.New[T](mem)
    }

    // allocating another slice
    slice := arena.MakeSlice[T](mem, 100, 200)
}
  • If you want to use any object that has been already released by the arena then you can clone the object to get a shallow copy from the heap.
mem := arena.NewArena()

// arena allocated here
obj1 := arena.New[T](mem)
//heap allocated here
obj2 := arena.Clone(obj1)
//this will be false
fmt.Println(obj1 == obj2)

mem.Free()
//from here onwards obj2 can be safely used
  • If you want to use the arena with reflect package
var typ = reflect.TypeOf((*T)(nil)).Elem()

mem := arena.NewArena()
defer mem.Free()

value := reflect.ArenaNew(mem, typ)
fmt.Println(value.interface().(*T))

Address sanitizer

Arena allows you to detect invalid storage patterns using the usage sanitizer(asan) and memory sanitizer(msan)

package main

import "arena"

type T struct {
    Num int
}

func main() {
    mem := arena.NewArena()
    o := arena.New[T](mem)
    mem.Free()
    o.Num = 123
    // after freeing up, this kind of use is incorrect
}

the storage allocations can be checked using the command to use the address sanitizer that provides the verbose error message

go run -asan main.go

accessed data from freed user arena 0x40c0007ff7f8
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x40c0007ff7f8 pc=0x4603d9]

goroutine 1 [running]:
runtime.throw({0x471778?, 0x404699?})
    /go/src/runtime/panic.go:1047 +0x5d fp=0x10c000067ef0 sp=0x10c000067ec0 pc=0x43193d
runtime.sigpanic()
    /go/src/runtime/signal_unix.go:851 +0x28a fp=0x10c000067f50 sp=0x10c000067ef0 pc=0x445b8a
main.main()
    /workspace/main.go:15 +0x79 fp=0x10c000067f80 sp=0x10c000067f50 pc=0x4603d9
runtime.main()
    /go/src/runtime/proc.go:250 +0x207 fp=0x10c000067fe0 sp=0x10c000067f80 pc=0x434227
runtime.goexit()
    /go/src/runtime/asm_amd64.s:1598 +0x1 fp=0x10c000067fe8 sp=0x10c000067fe0 pc=0x45c5a1

Slices

If a slice has been created using MakeSlice , there is a possible chance that the slice needs to be grown to hold more elements than a new slice needs to be allocated, or else the slice will be moved to the heap when growing with append.

// allocating a []string
slice := MakeSlice[string](mem, length, capacity)
// empty the slice from arena
slice := arena.MakeSlice[string](mem, 0, 0)
// the slice is on the heap now
slice = append(slice, "")

In this case, other data structures also can be used such as Linked List.

At this moment arenas don't support Maps. For strings, direct allocation is not allowed. Nil Arenas are not valid. Talking about performance, then Google has achieved 15% savings in CPU usage in huge apps.

Thanks for reading up to here.