package arena_test import ( "errors" "fmt" "runtime" "testing" "unsafe" "git.brut.systems/judah/xx/arena" "git.brut.systems/judah/xx/mem" ) func BenchmarkAlloc_New_Small(b *testing.B) { var last *int for i := range b.N { v := new(int) *v = i last = v if i%1000 == 0 { last = nil } } runtime.KeepAlive(last) } func BenchmarkAlloc_Closure_Small(b *testing.B) { alloc := arena.Pool[int](16) var last *int for i := range b.N { v := arena.New[int](alloc) *v = i last = v if i%1000 == 0 { arena.Reset(alloc) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Interface_Small(b *testing.B) { alloc := NewLinear(16 * mem.Kilobyte) var last *int for i := range b.N { v := New[int](&alloc) *v = i last = v if i%1000 == 0 { Reset(&alloc) } } runtime.KeepAlive(last) } type large struct{ a, b, c, d, e, f, g, h, i int } func BenchmarkAlloc_New_Large(b *testing.B) { var last *large for i := range b.N { v := new(large) v.e = i last = v if i%1000 == 0 { last = nil } } runtime.KeepAlive(last) } func BenchmarkAlloc_Closure_Large(b *testing.B) { alloc := arena.Linear(128 * mem.Kilobyte) var last *large for i := range b.N { v := arena.New[large](alloc) v.e = i last = v if i%1000 == 0 { arena.Reset(alloc) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Interface_Large(b *testing.B) { alloc := NewLinear(128 * mem.Kilobyte) var last *large for i := range b.N { v := New[large](&alloc) v.e = i last = v if i%1000 == 0 { Reset(&alloc) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Closure_HotPath(b *testing.B) { alloc := arena.Chunked(1 * mem.Kilobyte) var ( lastlarge *large lastsmall *int ) for i := range b.N { if i%2 == 0 { lastsmall = arena.New[int](alloc) } else { lastlarge = arena.New[large](alloc) } arena.Reset(alloc) } runtime.KeepAlive(lastlarge) runtime.KeepAlive(lastsmall) } func BenchmarkAlloc_Interface_HotPath(b *testing.B) { alloc := NewLinear(8 * mem.Kilobyte) var ( lastlarge *large lastsmall *int ) for i := range b.N { if i%2 == 0 { lastsmall = New[int](&alloc) } else { lastlarge = New[large](&alloc) } Reset(&alloc) } runtime.KeepAlive(lastlarge) runtime.KeepAlive(lastsmall) } type Allocator interface { Proc(a arena.Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) } func New[T any](a Allocator) *T { ptr, err := a.Proc(arena.ACTION_ALLOC, mem.Sizeof[T](), mem.Alignof[T](), nil) if err != nil { panic(err) } return (*T)(ptr) } func Reset(a Allocator) { if _, err := a.Proc(arena.ACTION_RESET, 0, 0, nil); err != nil { panic(err) } } type Linear struct { data []byte maxsize uintptr offset uintptr } func NewLinear(maxsize uintptr) Linear { return Linear{ data: make([]byte, maxsize), maxsize: maxsize, } } func (l *Linear) Proc(a arena.Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) { switch a { case arena.ACTION_ALLOC: aligned := mem.AlignForward(size, align) if l.offset+aligned > l.maxsize { return nil, errors.New(fmt.Sprintf("Linear: out of memory - %d bytes requested, (%d/%d) bytes available", size, l.maxsize-l.offset, l.maxsize)) } ptr := &l.data[l.offset] l.offset += aligned return unsafe.Pointer(ptr), nil case arena.ACTION_RESET: clear(l.data) l.offset = 0 case arena.ACTION_SAVE: *watermark = l.offset case arena.ACTION_RESTORE: l.offset = *watermark default: panic("unimplemented action: " + a.String()) } return nil, nil }