279 lines
4.5 KiB
Go
279 lines
4.5 KiB
Go
package arena_test
|
|
|
|
import (
|
|
"errors"
|
|
"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.Ring[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.Linear(256)
|
|
|
|
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(256)
|
|
|
|
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)
|
|
}
|
|
|
|
func BenchmarkAlloc_Closure_Wrapped(b *testing.B) {
|
|
alloc := arena.Pinned(arena.Pinned(arena.Pinned(arena.Linear(256))))
|
|
|
|
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_Wrapped(b *testing.B) {
|
|
alloc := NewPinned(NewPinned(NewPinned(NewLinear(256))))
|
|
|
|
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("linear: out of memory")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type Pinned struct {
|
|
arena Allocator
|
|
pinner runtime.Pinner
|
|
}
|
|
|
|
func NewPinned(arena Allocator) *Pinned {
|
|
return &Pinned{arena: arena}
|
|
}
|
|
|
|
func (p *Pinned) Proc(a arena.Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
ptr, err := p.arena.Proc(a, size, align, watermark)
|
|
if err != nil {
|
|
return ptr, err
|
|
}
|
|
|
|
if a == arena.ACTION_RESET {
|
|
p.pinner.Unpin()
|
|
} else {
|
|
p.pinner.Pin(ptr)
|
|
}
|
|
|
|
return ptr, err
|
|
}
|