diff --git a/README.md b/README.md index 4feb17a..4287c27 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,35 @@ func main() { By default, the arena implementation is not thread-safe, meaning it is not safe to access it concurrently from different goroutines. If the specific use case requires concurrent access, the library provides the `NewConcurrentArena` function, to which a base arena is passed and it returns a new instance that can be accessed concurrently. +## Benchmarks + +``` +BenchmarkRuntimeNewObject/100-8 1394955 846.6 ns/op 800 B/op 100 allocs/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 +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 +``` + ## Contributing We welcome contributions from the community! If you'd like to contribute, please fork the repository, make your changes, and submit a pull request. diff --git a/go.mod b/go.mod index 9860463..11d2ccd 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ortuman/memnuke +module github.com/ortuman/nuke go 1.21.7 diff --git a/slab_arena_test.go b/slab_arena_test.go index b8a470b..cad6aea 100644 --- a/slab_arena_test.go +++ b/slab_arena_test.go @@ -3,6 +3,7 @@ package nuke import ( + "fmt" "runtime" "testing" "time" @@ -97,3 +98,124 @@ func isSlabArenaPtr(a Arena, ptr unsafe.Pointer) bool { } return false } + +func BenchmarkRuntimeNewObject(b *testing.B) { + a := newRuntimeAllocator[int]() + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.new() + } + } + }) + } +} + +func BenchmarkSlabArenaNewObject(b *testing.B) { + slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB) + + a := newArenaAllocator[int](slabArena) + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.new() + } + a.(*arenaAllocator[int]).a.Reset(false) + } + }) + } +} + +func BenchmarkConcurrentSlabArenaNewObject(b *testing.B) { + slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB) + + a := newArenaAllocator[int](NewConcurrentArena(slabArena)) + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.new() + } + a.(*arenaAllocator[int]).a.Reset(false) + } + }) + } +} + +func BenchmarkRuntimeMakeSlice(b *testing.B) { + a := newRuntimeAllocator[int]() + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.makeSlice(0, 256) + } + } + }) + } +} + +func BenchmarkSlabArenaMakeSlice(b *testing.B) { + slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB) + + a := newArenaAllocator[int](slabArena) + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.makeSlice(0, 256) + } + a.(*arenaAllocator[int]).a.Reset(false) + } + }) + } +} + +func BenchmarkConcurrentSlabArenaMakeSlice(b *testing.B) { + slabArena := NewSlabArena(1024*1024, 128) // 1Mb slab size (128 MB) + + a := newArenaAllocator[int](NewConcurrentArena(slabArena)) + for _, objectCount := range []int{100, 1_000, 10_000, 100_000} { + b.Run(fmt.Sprintf("%d", objectCount), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for j := 0; j < objectCount; j++ { + _ = a.makeSlice(0, 256) + } + a.(*arenaAllocator[int]).a.Reset(false) + } + }) + } +} + +type allocator[T any] interface { + new() *T + makeSlice(len, cap int) []T +} + +type runtimeAllocator[T any] struct{} + +func newRuntimeAllocator[T any]() allocator[T] { + return &runtimeAllocator[T]{} +} + +func (r *runtimeAllocator[T]) new() *T { return new(T) } +func (r *runtimeAllocator[T]) makeSlice(len, cap int) []T { return make([]T, len, cap) } + +type arenaAllocator[T any] struct { + a Arena +} + +func newArenaAllocator[T any](a Arena) allocator[T] { + return &arenaAllocator[T]{a: a} +} + +func (r *arenaAllocator[T]) new() *T { return New[T](r.a) } +func (r *arenaAllocator[T]) makeSlice(len, cap int) []T { return MakeSlice[T](r.a, len, cap) }