package alloc_test import ( "errors" "fmt" "runtime" "testing" "unsafe" "git.brut.systems/judah/xx/alloc" "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) { allocator := alloc.Pool[int](16) var last *int for i := range b.N { v := alloc.New[int](allocator) *v = i last = v if i%1000 == 0 { alloc.Reset(allocator) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Interface_Small(b *testing.B) { allocator := NewLinear(16 * mem.Kilobyte) var last *int for i := range b.N { v := New[int](&allocator) *v = i last = v if i%1000 == 0 { Reset(&allocator) } } 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) { allocator := alloc.Linear(128 * mem.Kilobyte) var last *large for i := range b.N { v := alloc.New[large](allocator) v.e = i last = v if i%1000 == 0 { alloc.Reset(allocator) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Interface_Large(b *testing.B) { allocator := NewLinear(128 * mem.Kilobyte) var last *large for i := range b.N { v := New[large](&allocator) v.e = i last = v if i%1000 == 0 { Reset(&allocator) } } runtime.KeepAlive(last) } func BenchmarkAlloc_Closure_HotPath(b *testing.B) { allocator := alloc.Chunked(1 * mem.Kilobyte) var ( lastlarge *large lastsmall *int ) for i := range b.N { if i%2 == 0 { lastsmall = alloc.New[int](allocator) } else { lastlarge = alloc.New[large](allocator) } alloc.Reset(allocator) } runtime.KeepAlive(lastlarge) runtime.KeepAlive(lastsmall) } func BenchmarkAlloc_Interface_HotPath(b *testing.B) { allocator := NewLinear(8 * mem.Kilobyte) var ( lastlarge *large lastsmall *int ) for i := range b.N { if i%2 == 0 { lastsmall = New[int](&allocator) } else { lastlarge = New[large](&allocator) } Reset(&allocator) } runtime.KeepAlive(lastlarge) runtime.KeepAlive(lastsmall) } type Allocator interface { Proc(a alloc.Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) } func New[T any](a Allocator) *T { ptr, err := a.Proc(alloc.ActionAlloc, mem.SizeOf[T](), mem.AlignOf[T](), nil) if err != nil { panic(err) } return (*T)(ptr) } func Reset(a Allocator) { if _, err := a.Proc(alloc.ActionReset, 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 alloc.Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) { switch a { case alloc.ActionAlloc: 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 alloc.ActionReset: clear(l.data) l.offset = 0 case alloc.ActionSave: *watermark = l.offset case alloc.ActionRestore: l.offset = *watermark default: panic("unimplemented action: " + a.String()) } return nil, nil }