Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renamed slab arena to monotonic arena #18

Merged
merged 1 commit into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ import (
type Foo struct { A int }

func main() {
// Initialize a new memory arena with a slab size of 256KB
// Initialize a new monotonic arena with a buffer size of 256KB
// and a max memory size of 20MB.
arena := nuke.NewSlabArena(256*1024, 20*1024*1024)
arena := nuke.NewMonotonicArena(256*1024, 80)

// Allocate a new object of type Foo.
fooRef := nuke.New[Foo](arena)
Expand All @@ -63,7 +63,7 @@ func main() {

// ...

// When done, reset the arena (releasing slab buffer memory).
// When done, reset the arena (releasing monotonic buffer memory).
arena.Reset(true)

// From here on, any arena reference is invalid.
Expand All @@ -76,7 +76,7 @@ Additionally, we can inject a memory arena as part of a context, with the purpos
```go
func httpHandler(w http.ResponseWriter, r *http.Request) {
// Inject memory arena into request context.
arena := nuke.NewSlabArena(64*1024, 1024*1024)
arena := nuke.NewMonotonicArena(64*1024, 10)
defer arena.Reset(true)

ctx := nuke.InjectContextArena(r.Context(), arena)
Expand Down Expand Up @@ -112,7 +112,7 @@ import (

func main() {
arena := nuke.NewConcurrentArena(
nuke.NewSlabArena(256*1024, 20*1024*1024),
nuke.NewMonotonicArena(256*1024, 20),
)
defer arena.Reset(true)

Expand All @@ -130,26 +130,26 @@ BenchmarkRuntimeNewObject/100-8 1394955 846.6 ns/op
BenchmarkRuntimeNewObject/1000-8 143031 8357 ns/op 8000 B/op 1000 allocs/op
BenchmarkRuntimeNewObject/10000-8 14371 83562 ns/op 80000 B/op 10000 allocs/op
BenchmarkRuntimeNewObject/100000-8 1428 835474 ns/op 800005 B/op 100000 allocs/op
BenchmarkSlabArenaNewObject/100-8 124495 15469 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaNewObject/1000-8 76744 19602 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaNewObject/10000-8 24104 50845 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaNewObject/100000-8 3282 366044 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaNewObject/100-8 90392 16679 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaNewObject/1000-8 43753 29823 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaNewObject/10000-8 8037 149923 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaNewObject/100000-8 879 1364377 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaNewObject/100-8 124495 15469 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaNewObject/1000-8 76744 19602 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaNewObject/10000-8 24104 50845 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaNewObject/100000-8 3282 366044 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaNewObject/100-8 90392 16679 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaNewObject/1000-8 43753 29823 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaNewObject/10000-8 8037 149923 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaNewObject/100000-8 879 1364377 ns/op 0 B/op 0 allocs/op
BenchmarkRuntimeMakeSlice/100-8 58166 19684 ns/op 204800 B/op 100 allocs/op
BenchmarkRuntimeMakeSlice/1000-8 5916 196412 ns/op 2048010 B/op 1000 allocs/op
BenchmarkRuntimeMakeSlice/10000-8 600 1965622 ns/op 20480106 B/op 10001 allocs/op
BenchmarkRuntimeMakeSlice/100000-8 60 19664140 ns/op 204801155 B/op 100012 allocs/op
BenchmarkSlabArenaMakeSlice/100-8 166300 14520 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaMakeSlice/1000-8 43785 36938 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaMakeSlice/10000-8 2707 427398 ns/op 0 B/op 0 allocs/op
BenchmarkSlabArenaMakeSlice/100000-8 87 14048963 ns/op 70582284 B/op 34464 allocs/op
BenchmarkConcurrentSlabArenaMakeSlice/100-8 91959 17944 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaMakeSlice/1000-8 27384 42790 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaMakeSlice/10000-8 2406 480474 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentSlabArenaMakeSlice/100000-8 84 14702775 ns/op 70582280 B/op 34464 allocs/op
BenchmarkMonotonicArenaMakeSlice/100-8 166300 14520 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaMakeSlice/1000-8 43785 36938 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaMakeSlice/10000-8 2707 427398 ns/op 0 B/op 0 allocs/op
BenchmarkMonotonicArenaMakeSlice/100000-8 87 14048963 ns/op 70582284 B/op 34464 allocs/op
BenchmarkConcurrentMonotonicArenaMakeSlice/100-8 91959 17944 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaMakeSlice/1000-8 27384 42790 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaMakeSlice/10000-8 2406 480474 ns/op 0 B/op 0 allocs/op
BenchmarkConcurrentMonotonicArenaMakeSlice/100000-8 84 14702775 ns/op 70582280 B/op 34464 allocs/op
```

## Contributing
Expand Down
40 changes: 20 additions & 20 deletions slab_arena.go → monotonic_arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import (
"unsafe"
)

type slabArena struct {
slabs []*slab
type monotonicArena struct {
buffers []*monotonicBuffer
}

type slab struct {
type monotonicBuffer struct {
ptr unsafe.Pointer
offset int
size int
}

func newSlab(size int) *slab {
return &slab{size: size}
func newMonotonicBuffer(size int) *monotonicBuffer {
return &monotonicBuffer{size: size}
}

func (s *slab) alloc(size int) (unsafe.Pointer, bool) {
func (s *monotonicBuffer) alloc(size int) (unsafe.Pointer, bool) {
if s.ptr == nil {
buf := make([]byte, s.size) // allocate slab buffer lazily
buf := make([]byte, s.size) // allocate monotonic buffer lazily
s.ptr = unsafe.Pointer(unsafe.SliceData(buf))
}
if s.availableBytes() < size {
Expand All @@ -34,7 +34,7 @@ func (s *slab) alloc(size int) (unsafe.Pointer, bool) {
return ptr, true
}

func (s *slab) reset(release bool) {
func (s *monotonicBuffer) reset(release bool) {
if s.offset == 0 {
return
}
Expand All @@ -47,7 +47,7 @@ func (s *slab) reset(release bool) {
}
}

func (s *slab) zeroOutBuffer() {
func (s *monotonicBuffer) zeroOutBuffer() {
b := unsafe.Slice((*byte)(s.ptr), s.size)

// This piece of code will be translated into a runtime.memclrNoHeapPointers
Expand All @@ -59,23 +59,23 @@ func (s *slab) zeroOutBuffer() {
}
}

func (s *slab) availableBytes() int {
func (s *monotonicBuffer) availableBytes() int {
return s.size - s.offset
}

// NewSlabArena creates a new slab arena with a specified number of slabs and slab size.
func NewSlabArena(slabSize, slabCount int) Arena {
a := &slabArena{}
for i := 0; i < slabCount; i++ {
a.slabs = append(a.slabs, newSlab(slabSize))
// NewMonotonicArena creates a new monotonic arena with a specified number of buffers and a buffer size.
func NewMonotonicArena(bufferSize, bufferCount int) Arena {
a := &monotonicArena{}
for i := 0; i < bufferCount; i++ {
a.buffers = append(a.buffers, newMonotonicBuffer(bufferSize))
}
return a
}

// Alloc satisfies the Arena interface.
func (a *slabArena) Alloc(size int) unsafe.Pointer {
for i := 0; i < len(a.slabs); i++ {
ptr, ok := a.slabs[i].alloc(size)
func (a *monotonicArena) Alloc(size int) unsafe.Pointer {
for i := 0; i < len(a.buffers); i++ {
ptr, ok := a.buffers[i].alloc(size)
if ok {
return ptr
}
Expand All @@ -84,8 +84,8 @@ func (a *slabArena) Alloc(size int) unsafe.Pointer {
}

// Reset satisfies the Arena interface.
func (a *slabArena) Reset(release bool) {
for _, s := range a.slabs {
func (a *monotonicArena) Reset(release bool) {
for _, s := range a.buffers {
s.reset(release)
}
}
58 changes: 29 additions & 29 deletions slab_arena_test.go → monotonic_arena_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,42 @@ import (
"github.com/stretchr/testify/require"
)

func TestSlabArenaAllocateObject(t *testing.T) {
arena := NewSlabArena(8182, 1) // 8KB
func TestMonotonicArenaAllocateObject(t *testing.T) {
arena := NewMonotonicArena(8182, 1) // 8KB

var refs []*int
for i := 0; i < 1_000; i++ {
refs = append(refs, New[int](arena))
}

for i := 0; i < 1_000; i++ {
require.True(t, isSlabArenaPtr(arena, unsafe.Pointer(refs[i])))
require.True(t, isMonotonicArenaPtr(arena, unsafe.Pointer(refs[i])))
}
}

func TestSlabArenaSendObjectToHeap(t *testing.T) {
func TestMonotonicArenaAllocateSlice(t *testing.T) {}

func TestMonotonicArenaSendObjectToHeap(t *testing.T) {
var x int
arena := NewSlabArena(2*int(unsafe.Sizeof(x)), 1) // 2 ints room
arena := NewMonotonicArena(2*int(unsafe.Sizeof(x)), 1) // 2 ints room

// Send the first two ints to the arena
require.True(t, isSlabArenaPtr(arena, unsafe.Pointer(New[int](arena))))
require.True(t, isSlabArenaPtr(arena, unsafe.Pointer(New[int](arena))))
require.True(t, isMonotonicArenaPtr(arena, unsafe.Pointer(New[int](arena))))
require.True(t, isMonotonicArenaPtr(arena, unsafe.Pointer(New[int](arena))))

// Send last one to the heap
require.False(t, isSlabArenaPtr(arena, unsafe.Pointer(New[int](arena))))
require.False(t, isMonotonicArenaPtr(arena, unsafe.Pointer(New[int](arena))))
}

func TestSlabArenaReset(t *testing.T) {
arena := NewSlabArena(1024, 1).(*slabArena) // one slab of 1KB
func TestMonotonicArenaReset(t *testing.T) {
arena := NewMonotonicArena(1024, 1).(*monotonicArena) // one monotonic buffer of 1KB

// Allocate slab buffer
// Allocate monotonic buffer
_ = New[int](arena)

// Configure finalizer
gced := make(chan bool)
runtime.SetFinalizer((*byte)(arena.slabs[0].ptr), func(*byte) {
runtime.SetFinalizer((*byte)(arena.buffers[0].ptr), func(*byte) {
close(gced)
})

Expand Down Expand Up @@ -81,11 +83,9 @@ func TestSlabArenaReset(t *testing.T) {
_ = New[int](arena)
}

func TestSlabArenaAllocateSlice(t *testing.T) {}

func isSlabArenaPtr(a Arena, ptr unsafe.Pointer) bool {
sa := a.(*slabArena)
for _, s := range sa.slabs {
func isMonotonicArenaPtr(a Arena, ptr unsafe.Pointer) bool {
ma := a.(*monotonicArena)
for _, s := range ma.buffers {
if s.ptr == nil {
break
}
Expand Down Expand Up @@ -113,10 +113,10 @@ func BenchmarkRuntimeNewObject(b *testing.B) {
}
}

func BenchmarkSlabArenaNewObject(b *testing.B) {
slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB)
func BenchmarkMonotonicArenaNewObject(b *testing.B) {
monotonicArena := NewMonotonicArena(1024*1024, 128) // 1Mb buffer size (128 MB)

a := newArenaAllocator[int](slabArena)
a := newArenaAllocator[int](monotonicArena)
for _, objectCount := range []int{100, 1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) {
b.ReportAllocs()
Expand All @@ -130,10 +130,10 @@ func BenchmarkSlabArenaNewObject(b *testing.B) {
}
}

func BenchmarkConcurrentSlabArenaNewObject(b *testing.B) {
slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB)
func BenchmarkConcurrentMonotonicArenaNewObject(b *testing.B) {
monotonicArena := NewMonotonicArena(1024*1024, 128) // 1Mb buffer size (128 MB)

a := newArenaAllocator[int](NewConcurrentArena(slabArena))
a := newArenaAllocator[int](NewConcurrentArena(monotonicArena))
for _, objectCount := range []int{100, 1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) {
b.ReportAllocs()
Expand Down Expand Up @@ -161,10 +161,10 @@ func BenchmarkRuntimeMakeSlice(b *testing.B) {
}
}

func BenchmarkSlabArenaMakeSlice(b *testing.B) {
slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB)
func BenchmarkMonotonicArenaMakeSlice(b *testing.B) {
monotonicArena := NewMonotonicArena(1024*1024, 128) // 1Mb buffer size (128 MB)

a := newArenaAllocator[int](slabArena)
a := newArenaAllocator[int](monotonicArena)
for _, objectCount := range []int{100, 1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) {
b.ReportAllocs()
Expand All @@ -178,10 +178,10 @@ func BenchmarkSlabArenaMakeSlice(b *testing.B) {
}
}

func BenchmarkConcurrentSlabArenaMakeSlice(b *testing.B) {
slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB)
func BenchmarkConcurrentMonotonicArenaMakeSlice(b *testing.B) {
monotonicArena := NewMonotonicArena(1024*1024, 128) // 1Mb buffer size (128 MB)

a := newArenaAllocator[int](NewConcurrentArena(slabArena))
a := newArenaAllocator[int](NewConcurrentArena(monotonicArena))
for _, objectCount := range []int{100, 1_000, 10_000, 100_000} {
b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) {
b.ReportAllocs()
Expand Down
Loading