Skip to content

Commit

Permalink
Change threshold allocator to fallback allocator, improve speeds
Browse files Browse the repository at this point in the history
  • Loading branch information
CannibalVox committed Aug 21, 2021
1 parent f220fdf commit 1c858b1
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 67 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ More importantly, it provides an allocator `FixedBlockAllocator` which sits on t
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.
* `FallbackAllocator` - Accepts a FixedBlockAllocator and one other allocator- if the malloc can fit in the FBA, it uses that, otherwise it mallocs in the other allocator. You can use this to fall back on 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 is optimized for `FreeAll` and ordinary frees have a cost of O(N)

### Are these thread-safe?
Expand All @@ -40,6 +40,14 @@ BenchmarkFBAGrowShrink
BenchmarkFBAGrowShrink-16 64682006 34.83 ns/op
```

3-Layer Fallback
```
BenchmarkMultilayerTemporaryData
BenchmarkMultilayerTemporaryData-16 72288720 17.06 ns/op
BenchmarkMultilayerGrowShrink
BenchmarkMultilayerGrowShrink-16 48367983 35.78 ns/op
```

Arena
```
BenchmarkArenaTemporaryData
Expand Down
56 changes: 56 additions & 0 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,59 @@ func BenchmarkFBAGrowShrink(b *testing.B) {
alloc.Free(ptrs[i])
}
}

func BenchmarkMultilayerTemporaryData(b *testing.B) {
defAlloc := &DefaultAllocator{}
lowerLevel, err := CreateFixedBlockAllocator(defAlloc, 256, 8, 8)
if err != nil {
b.Fail()
}
higherLevel, err := CreateFixedBlockAllocator(defAlloc, 2048, 256, 8)
if err != nil {
b.Fail()
}
alloc := CreateFallbackAllocator(higherLevel, defAlloc)
alloc = CreateFallbackAllocator(lowerLevel, alloc)
defer alloc.Destroy()

for i := 0; i < b.N; i++ {
size := 4
if i % 100 == 0 {
size = 512
} else if i % 10 == 0 {
size = 128
}
a := alloc.Malloc(size)
alloc.Free(a)
}
}

func BenchmarkMultilayerGrowShrink(b *testing.B) {
defAlloc := &DefaultAllocator{}
lowerLevel, err := CreateFixedBlockAllocator(defAlloc, 1024*1024, 8, 8)
if err != nil {
b.Fail()
}
higherLevel, err := CreateFixedBlockAllocator(defAlloc, 32*1024*1024, 256, 8)
if err != nil {
b.Fail()
}
alloc := CreateFallbackAllocator(higherLevel, defAlloc)
alloc = CreateFallbackAllocator(lowerLevel, alloc)
defer alloc.Destroy()

ptrs := make([]unsafe.Pointer, b.N, b.N)
for i := 0; i < b.N; i++ {
size := 4
if i % 1000 == 0 {
size = 512
} else if i % 100 == 0 {
size = 128
}
ptrs[i] = alloc.Malloc(size)
}

for i := 0; i < b.N; i++ {
alloc.Free(ptrs[i])
}
}
36 changes: 36 additions & 0 deletions fallback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cgoalloc

import (
"unsafe"
)

type FallbackAllocator struct {
fixedBlock FixedBlockAllocator
fallback Allocator
}

func CreateFallbackAllocator(fixedBlock FixedBlockAllocator, fallback Allocator) *FallbackAllocator {
return &FallbackAllocator{
fixedBlock: fixedBlock,
fallback: fallback,
}
}

func (a *FallbackAllocator) Malloc(size int) unsafe.Pointer {
if size > a.fixedBlock.BlockSize() {
return a.fallback.Malloc(size)
}

return a.fixedBlock.Malloc(size)
}

func (a *FallbackAllocator) Free(ptr unsafe.Pointer) {
if !a.fixedBlock.TryFree(ptr) {
a.fallback.Free(ptr)
}
}

func (a *FallbackAllocator) Destroy() {
a.fixedBlock.Destroy()
a.fallback.Destroy()
}
9 changes: 7 additions & 2 deletions threshold_test.go → fallback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import (

func TestThresholdAlloc(t *testing.T) {
test1 := CreateTestAllocator(t, &DefaultAllocator{})
test2 := CreateTestAllocator(t, &DefaultAllocator{})

thresholdAllocator := CreateThresholdAllocator(64, test1, test2)
test2FBA, err := CreateFixedBlockAllocator(&DefaultAllocator{}, 64, 64, 8)
if err != nil {
t.FailNow()
}
test2 := CreateTestAllocator(t, test2FBA)

thresholdAllocator := CreateFallbackAllocator(test2, test1)
defer thresholdAllocator.Destroy()

a1 := thresholdAllocator.Malloc(8)
Expand Down
45 changes: 35 additions & 10 deletions fixed_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import (
"unsafe"
)

type FixedBlockAllocator struct {
type FixedBlockAllocator interface {
Allocator
TryFree(ptr unsafe.Pointer) bool
BlockSize() int
}

type fixedBlockAllocatorImpl struct {
inner Allocator

nextPageTicket uint
Expand All @@ -27,15 +33,15 @@ type FixedBlockAllocator struct {
freeBlockQueue PagePQueue
}

func CreateFixedBlockAllocator(inner Allocator, pageSize , blockSize, alignment uintptr) (*FixedBlockAllocator, error) {
func CreateFixedBlockAllocator(inner Allocator, pageSize , blockSize, alignment uintptr) (FixedBlockAllocator, error) {
if blockSize % alignment != 0 {
return nil, errors.New("fixed block allocator: blocksize must be a multiple of alignment")
}
if pageSize % blockSize != 0 {
return nil, errors.New("fixed block allocator: pagesize must be a multiple of blocksize")
}

return &FixedBlockAllocator{
return &fixedBlockAllocatorImpl{
inner: inner,

allFreeBlocks: 0,
Expand All @@ -50,7 +56,9 @@ func CreateFixedBlockAllocator(inner Allocator, pageSize , blockSize, alignment
}, nil
}

func (a *FixedBlockAllocator) Destroy() {
func (a *fixedBlockAllocatorImpl) BlockSize() int { return int(a.blockSize)}

func (a *fixedBlockAllocatorImpl) Destroy() {
blocks := a.blocksPerPage * len(a.pages)
if blocks > a.allFreeBlocks {
panic("fixedblockallocator: attempted to Destroy, but not all allocations had been freed")
Expand All @@ -61,7 +69,7 @@ func (a *FixedBlockAllocator) Destroy() {
}
}

func (a *FixedBlockAllocator) allocatePage() {
func (a *fixedBlockAllocatorImpl) allocatePage() {
// Allocate page memory
size := int(a.pageSize+a.alignment)
pagePtr := a.inner.Malloc(size)
Expand Down Expand Up @@ -95,7 +103,7 @@ func (a *FixedBlockAllocator) allocatePage() {
a.pages[pageStart] = page
}

func (a *FixedBlockAllocator) deallocatePage(page *Page) {
func (a *fixedBlockAllocatorImpl) deallocatePage(page *Page) {
for i := 0; i < len(a.pageStarts); i++ {
if a.pageStarts[i] == page.pageStart {
a.pageStarts = append(a.pageStarts[:i], a.pageStarts[i+1:]...)
Expand All @@ -110,7 +118,7 @@ func (a *FixedBlockAllocator) deallocatePage(page *Page) {
a.inner.Free(unsafe.Pointer(page.pageStart))
}

func (a *FixedBlockAllocator) Malloc(size int) unsafe.Pointer {
func (a *fixedBlockAllocatorImpl) Malloc(size int) unsafe.Pointer {
if size > int(a.blockSize) {
panic("fixed block allocator: requested allocation larger than block size")
}
Expand All @@ -136,19 +144,34 @@ func (a *FixedBlockAllocator) Malloc(size int) unsafe.Pointer {
return block
}

func (a *FixedBlockAllocator) Free(block unsafe.Pointer) {
func (a *fixedBlockAllocatorImpl) Free(block unsafe.Pointer) {
if !a.TryFree(block) {
panic("fixed block allocator: attempted to free a block not located in an allocated page")
}
}

func (a *fixedBlockAllocatorImpl) TryFree(block unsafe.Pointer) bool {
pageStartsLen := len(a.pageStarts)
if pageStartsLen == 0 {
return false
}

//Find the page this block belongs to
blockPtr := uintptr(block)
pageStartIdx := sort.Search(len(a.pageStarts), func(i int) bool {
pageStartIdx := sort.Search(pageStartsLen, func(i int) bool {
start := a.pageStarts[i]
size := a.pageSize+a.alignment
end := start+size
return end > blockPtr
})

if pageStartIdx >= pageStartsLen {
return false
}

pageStart := a.pageStarts[pageStartIdx]
if pageStart > blockPtr {
panic("fixed block allocator: attempted to free a block not located in an allocated page")
return false
}
page := a.pages[pageStart]

Expand All @@ -165,4 +188,6 @@ func (a *FixedBlockAllocator) Free(block unsafe.Pointer) {
// We have twice as many blocks as we need & this page is unallocated
a.deallocatePage(page)
}

return true
}
22 changes: 21 additions & 1 deletion testalloc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,33 @@ func (a *TestAlloc) Malloc(size int) unsafe.Pointer {
return alloc
}

func (a *TestAlloc) Free(ptr unsafe.Pointer) {
func (a *TestAlloc) recordFree(ptr unsafe.Pointer) {
size, ok := a.allocSizes[ptr]
require.True(a.t, ok)

delete(a.allocSizes, ptr)
a.frees = append(a.frees, size)
}

func (a *TestAlloc) BlockSize() int {
fba, ok := a.inner.(FixedBlockAllocator)
require.True(a.t, ok, "testalloc: used testalloc as a fixedbufferallocator but it isn't wrapping a fixedbufferallocator")
return fba.BlockSize()
}

func (a *TestAlloc) TryFree(ptr unsafe.Pointer) bool {
fba, ok := a.inner.(FixedBlockAllocator)
require.True(a.t, ok, "testalloc: used testalloc as a fixedbufferallocator but it isn't wrapping a fixedbufferallocator")

freed := fba.TryFree(ptr)
if freed {
a.recordFree(ptr)
}
return freed
}

func (a *TestAlloc) Free(ptr unsafe.Pointer) {
a.recordFree(ptr)
a.inner.Free(ptr)
}

Expand Down
53 changes: 0 additions & 53 deletions threshold.go

This file was deleted.

0 comments on commit 1c858b1

Please sign in to comment.