xx/alloc/alloc_test.go

214 lines
3.6 KiB
Go

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
}