Hello, dear colleagues! Some time ago, I became interested in Rust and its approaches. You know, these borrow checkers that mess with your mind, lifetimes and all that stuff. Interesting, but complex to grasp. Is there something similar in Go? And I found it. A stillborn child 🥺
Go is known for its efficient automatic Garbage Collection, GC, which greatly simplifies memory management for developers. However, for certain high-performance computing scenarios where a large number of short-lived objects are allocated and freed, GC overhead can become a bottleneck. To optimize such cases, Go engineers and community explored alternative approaches, one of which was memory arenas, introduced in Go 1.20.
Memory arena is a memory management concept where instead of allocating and freeing each object individually, a large, pre-defined block of memory (the arena itself) is allocated. Objects needed for a specific task or during a limited time are placed sequentially inside this arena. When all objects in the arena are no longer needed, the entire arena is freed at once.
This is their main idea - instead of having GC track and collect each small object separately, we group related objects in one block and free this block entirely.
Using arenas could potentially provide several significant benefits:
However, the arena concept is not without its drawbacks, especially in Go's context:
Theoretically, arenas could be useful in the following scenarios:
<aside> ⚠️
As you probably guessed, this is an experimental package. Its use in production is absolutely NOT RECOMMENDED!
</aside>
For experiments, you can use arenas by passing the GOEXPERIMENT=arenas
variable:
GOEXPERIMENT=arenas go run main.go
Let's look at a few examples:
package main
import (
"fmt"
"arena"
)
// Define a simple structure for example
type Data struct {
Value int
Label string
}
func main() {
// create arena
// arena.NewArena() creates a new memory block that will be our arena
a := arena.NewArena()
// defer arena freeing
// using defer a.Free() ensures that the arena will be freed
// when the main function completes its work. This simulates freeing
// all objects in the arena at once after their lifecycle ends
defer a.Free()
// allocating objects in the arena
// arena.New[T](a) allocates memory for one object of type T in arena 'a'
// arena.Make[T](a, len, cap) allocates memory for a slice of type T
// with given length and capacity in arena 'a'
// allocate Data structure in the arena
da := arena.New[Data](a)
da.Value = 100
da.Label = "first"
// allocate Data slice in the arena
// this will be a slice with length 5, capacity 10, memory allocated in the arena
dataSlice := arena.MakeSlice[Data](a, 5, 10)
// performing "calculations" with objects from the arena
// fill the slice and work with objects
sumOfValues := da.Value
for i := range dataSlice {
dataSlice[i].Value = i * 10
dataSlice[i].Label = fmt.Sprintf("Data %d", i)
sumOfValues += dataSlice[i].Value
// print addresses within the arena
fmt.Printf("object in slice [%d]: %+v (address: %p)\\n", i, &dataSlice[i], &dataSlice[i])
}
// finishing work with objects from the arena
// here logically ends the "lifecycle" of objects in the arena
// next defer a.Free() will trigger
fmt.Println("main function ending, arena will be freed")
// after a.Free() triggers, the memory occupied by da and dataSlice
// will be returned to the system/Go runtime
// accessing these objects after freeing the arena is unsafe and can lead
// to "use after free" errors in languages with manual memory management
// in Go runtime that supports arenas, this means the memory is freed
// and should not be used!
// attempting to access values (not pointers) might temporarily work,
// but pointers become invalid
}
<aside> ⚠️
Maps and nil arenas are not supported.
</aside>
Rust programmers may notice some conceptual similarity between the idea of arenas and lifetimes.