aboutsummaryrefslogtreecommitdiff
path: root/src/arena
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2022-08-12 23:25:56 +0000
committerMichael Knyszek <mknyszek@google.com>2022-10-12 20:23:36 +0000
commite0d01b8467b5cb9e68758932f50c3187374011ba (patch)
tree77734bcb0619d55db4e9c6627cb2368652f466de /src/arena
parent7866538d250e1693bacb6e5a29c74b01588155d5 (diff)
downloadgo-e0d01b8467b5cb9e68758932f50c3187374011ba.tar.gz
go-e0d01b8467b5cb9e68758932f50c3187374011ba.zip
arena: add experimental arena package
This change adds the arenas package and a function to reflect for allocating from an arena via reflection, but all the new API is placed behind a GOEXPERIMENT. For #51317. Change-Id: I026d46294e26ab386d74625108c19a0024fbcedc Reviewed-on: https://go-review.googlesource.com/c/go/+/423361 Reviewed-by: Cherry Mui <cherryyz@google.com> Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'src/arena')
-rw-r--r--src/arena/arena.go108
-rw-r--r--src/arena/arena_test.go42
2 files changed, 150 insertions, 0 deletions
diff --git a/src/arena/arena.go b/src/arena/arena.go
new file mode 100644
index 0000000000..35b2fbd2aa
--- /dev/null
+++ b/src/arena/arena.go
@@ -0,0 +1,108 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.arenas
+
+/*
+The arena package provides the ability to allocate memory for a collection
+of Go values and free that space manually all at once, safely. The purpose
+of this functionality is to improve efficiency: manually freeing memory
+before a garbage collection delays that cycle. Less frequent cycles means
+the CPU cost of the garbage collector is incurred less frequently.
+
+This functionality in this package is mostly captured in the Arena type.
+Arenas allocate large chunks of memory for Go values, so they're likely to
+be inefficient for allocating only small amounts of small Go values. They're
+best used in bulk, on the order of MiB of memory allocated on each use.
+
+Note that by allowing for this limited form of manual memory allocation
+that use-after-free bugs are possible with regular Go values. This package
+limits the impact of these use-after-free bugs by preventing reuse of freed
+memory regions until the garbage collector is able to determine that it is
+safe. Typically, a use-after-free bug will result in a fault and a helpful
+error message, but this package reserves the right to not force a fault on
+freed memory. That means a valid implementation of this package is to just
+allocate all memory the way the runtime normally would, and in fact, it
+reserves the right to occasionally do so for some Go values.
+*/
+package arena
+
+import (
+ "internal/reflectlite"
+ "unsafe"
+)
+
+// Arena represents a collection of Go values allocated and freed together.
+// Arenas are useful for improving efficiency as they may be freed back to
+// the runtime manually, though any memory obtained from freed arenas must
+// not be accessed once that happens. An Arena is automatically freed once
+// it is no longer referenced, so it must be kept alive (see runtime.KeepAlive)
+// until any memory allocated from it is no longer needed.
+//
+// An Arena must never be used concurrently by multiple goroutines.
+type Arena struct {
+ a unsafe.Pointer
+}
+
+// NewArena allocates a new arena.
+func NewArena() *Arena {
+ return &Arena{a: runtime_arena_newArena()}
+}
+
+// Free frees the arena (and all objects allocated from the arena) so that
+// memory backing the arena can be reused fairly quickly without garbage
+// collection overhead. Applications must not call any method on this
+// arena after it has been freed.
+func (a *Arena) Free() {
+ runtime_arena_arena_Free(a.a)
+ a.a = nil
+}
+
+// New creates a new *T in the provided arena. The *T must not be used after
+// the arena is freed. Accessing the value after free may result in a fault,
+// but this fault is also not guaranteed.
+func New[T any](a *Arena) *T {
+ return runtime_arena_arena_New(a.a, reflectlite.TypeOf((*T)(nil))).(*T)
+}
+
+// MakeSlice creates a new []T with the provided capacity and length. The []T must
+// not be used after the arena is freed. Accessing the underlying storage of the
+// slice after free may result in a fault, but this fault is also not guaranteed.
+func MakeSlice[T any](a *Arena, len, cap int) []T {
+ var sl []T
+ runtime_arena_arena_Slice(a.a, &sl, cap)
+ return sl[:len]
+}
+
+// Clone makes a shallow copy of the input value that is no longer bound to any
+// arena it may have been allocated from, returning the copy. If it was not
+// allocated from an arena, it is returned untouched. This function is useful
+// to more easily let an arena-allocated value out-live its arena.
+// T must be a pointer, a slice, or a string, otherwise this function will panic.
+func Clone[T any](s T) T {
+ return runtime_arena_heapify(s).(T)
+}
+
+//go:linkname reflect_arena_New reflect.arena_New
+func reflect_arena_New(a *Arena, typ any) any {
+ return runtime_arena_arena_New(a.a, typ)
+}
+
+//go:linkname runtime_arena_newArena
+func runtime_arena_newArena() unsafe.Pointer
+
+//go:linkname runtime_arena_arena_New
+func runtime_arena_arena_New(arena unsafe.Pointer, typ any) any
+
+// Mark as noescape to avoid escaping the slice header.
+//
+//go:noescape
+//go:linkname runtime_arena_arena_Slice
+func runtime_arena_arena_Slice(arena unsafe.Pointer, slice any, cap int)
+
+//go:linkname runtime_arena_arena_Free
+func runtime_arena_arena_Free(arena unsafe.Pointer)
+
+//go:linkname runtime_arena_heapify
+func runtime_arena_heapify(any) any
diff --git a/src/arena/arena_test.go b/src/arena/arena_test.go
new file mode 100644
index 0000000000..017c33c502
--- /dev/null
+++ b/src/arena/arena_test.go
@@ -0,0 +1,42 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.arenas
+
+package arena_test
+
+import (
+ "arena"
+ "testing"
+)
+
+type T1 struct {
+ n int
+}
+type T2 [1 << 20]byte // 1MiB
+
+func TestSmoke(t *testing.T) {
+ a := arena.NewArena()
+ defer a.Free()
+
+ tt := arena.New[T1](a)
+ tt.n = 1
+
+ ts := arena.MakeSlice[T1](a, 99, 100)
+ if len(ts) != 99 {
+ t.Errorf("Slice() len = %d, want 99", len(ts))
+ }
+ if cap(ts) != 100 {
+ t.Errorf("Slice() cap = %d, want 100", cap(ts))
+ }
+ ts[1].n = 42
+}
+
+func TestSmokeLarge(t *testing.T) {
+ a := arena.NewArena()
+ defer a.Free()
+ for i := 0; i < 10*64; i++ {
+ _ = arena.New[T2](a)
+ }
+}