Skip to content

Commit

Permalink
Performance measuring & improvements for arena
Browse files Browse the repository at this point in the history
  • Loading branch information
CannibalVox committed Aug 20, 2021
1 parent 3950887 commit f220fdf
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 16 deletions.
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Also available:

* `DefaultAllocator` - calls cgo for Malloc and Free
* `ThresholdAllocator` - If the malloc size is <= a provided value, use one allocator. Otherwise, use the other. Allocations made above the threshold size are stored in a map to enable `Free`. You can use this with a `FixedBlockAllocator` to use the default allocator for large requests. You could also use several to set up a multi-tiered FBA, I suppose.
* `ArenaAllocator` - sits on top of another allocator. Exposes a FreeAll method which will free all memory allocated through the ArenaAllocator.
* `ArenaAllocator` - sits on top of another allocator. Exposes a FreeAll method which will free all memory allocated through the ArenaAllocator. ArenaAllocator is optimized for `FreeAll` and ordinary frees have a cost of O(N)

### Are these thread-safe?

Expand All @@ -24,15 +24,26 @@ The DefaultAllocator is! And as slow as cgo is, it's still far faster than any l

In terms of memory overhead, it's kind of bad! I use a lot of maps and slices to track allocated-but-not-freed data. In terms of speed:

Default cgo
```
BenchmarkDefaultTemporaryData
BenchmarkDefaultTemporaryData-16 12688918 96.51 ns/op
BenchmarkFBATemporaryData
BenchmarkFBATemporaryData-16 100000000 11.07 ns/op
BenchmarkDefaultTemporaryData-16 12792590 94.58 ns/op
BenchmarkDefaultGrowShrink
BenchmarkDefaultGrowShrink-16 11640133 105.1 ns/op
BenchmarkDefaultGrowShrink-16 11286946 104.7 ns/op
```

Fixed Buffer
```
BenchmarkFBATemporaryData
BenchmarkFBATemporaryData-16 123561244 9.714 ns/op
BenchmarkFBAGrowShrink
BenchmarkFBAGrowShrink-16 61445820 34.88 ns/op
BenchmarkFBAGrowShrink-16 64682006 34.83 ns/op
```

Arena
```
BenchmarkArenaTemporaryData
BenchmarkArenaTemporaryData-16 40963460 29.24 ns/op
```

"It's fine!"
29 changes: 19 additions & 10 deletions arena.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,47 @@ import (
type ArenaAllocator struct {
inner Allocator

allocations map[unsafe.Pointer]bool
allocations []unsafe.Pointer
}

func CreateArenaAllocator(inner Allocator) *ArenaAllocator {
return &ArenaAllocator{
inner: inner,

allocations: make(map[unsafe.Pointer]bool),
allocations: make([]unsafe.Pointer, 0, 1),
}
}

func (a *ArenaAllocator) Malloc(size int) unsafe.Pointer {
alloc := a.inner.Malloc(size)
a.allocations[alloc] = true
a.allocations = append(a.allocations, alloc)
return alloc
}

func (a *ArenaAllocator) Free(ptr unsafe.Pointer) {
_, ok := a.allocations[ptr]
if !ok {
allocIndex := -1
for i := 0; i < len(a.allocations); i++ {
if a.allocations[i] == ptr {
allocIndex = i
break
}
}

if allocIndex < 0 {
panic("arenaallocator: attempted to free a pointer which had not been allocated with this allocator")
}
delete(a.allocations, ptr)

newEnd := len(a.allocations)-1
a.allocations[allocIndex] = a.allocations[newEnd]
a.allocations = a.allocations[:newEnd]

a.inner.Free(ptr)
}

func (a *ArenaAllocator) FreeAll() {
for ptr := range a.allocations {
a.inner.Free(ptr)
for i := 0; i < len(a.allocations); i++ {
a.inner.Free(a.allocations[i])
}
a.allocations = make(map[unsafe.Pointer]bool)
a.allocations = nil
}

func (a *ArenaAllocator) Destroy() {
Expand Down
15 changes: 15 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ func BenchmarkFBATemporaryData(b *testing.B) {
}
}

func BenchmarkArenaTemporaryData(b *testing.B) {
alloc, err := CreateFixedBlockAllocator(&DefaultAllocator{}, 4096, 64, 8)
if err != nil {
b.FailNow()
}
defer alloc.Destroy()

for i := 0; i < b.N/2; i++ {
arena := CreateArenaAllocator(alloc)
_ = arena.Malloc(64)
_ = arena.Malloc(64)
arena.FreeAll()
}
}

func BenchmarkDefaultGrowShrink(b *testing.B) {
alloc := &DefaultAllocator{}
defer alloc.Destroy()
Expand Down

0 comments on commit f220fdf

Please sign in to comment.