178 lines
3.5 KiB
Go
178 lines
3.5 KiB
Go
|
|
package allocator
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"math"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Minimum allocation block size
|
||
|
|
const MinAlloc = 256
|
||
|
|
|
||
|
|
var minBucketTier = int(math.Log2(MinAlloc))
|
||
|
|
|
||
|
|
var ErrOutOfMemory = errors.New("out of memory")
|
||
|
|
var ErrInvalidFree = errors.New("illegal free() call")
|
||
|
|
|
||
|
|
type Block struct {
|
||
|
|
Offset int
|
||
|
|
Size int
|
||
|
|
}
|
||
|
|
|
||
|
|
type T interface {
|
||
|
|
Alloc(int) (Block, error)
|
||
|
|
Free(int) error
|
||
|
|
}
|
||
|
|
|
||
|
|
// Buddy allocator implementation
|
||
|
|
type buddy struct {
|
||
|
|
free [][]Block
|
||
|
|
alloc map[int]int
|
||
|
|
top int
|
||
|
|
}
|
||
|
|
|
||
|
|
func New(size int) T {
|
||
|
|
if !IsPowerOfTwo(size) {
|
||
|
|
panic("allocator size must be a power of 2")
|
||
|
|
}
|
||
|
|
|
||
|
|
top := GetBucketTier(size)
|
||
|
|
free := make([][]Block, top+1)
|
||
|
|
free[top] = []Block{{Offset: 0, Size: size}}
|
||
|
|
|
||
|
|
return &buddy{
|
||
|
|
top: top,
|
||
|
|
free: free,
|
||
|
|
alloc: map[int]int{},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *buddy) Alloc(size int) (Block, error) {
|
||
|
|
tier := GetBucketTier(size)
|
||
|
|
block, err := f.getBlock(tier)
|
||
|
|
if err != nil {
|
||
|
|
return Block{}, err
|
||
|
|
}
|
||
|
|
f.alloc[block.Offset] = block.Size
|
||
|
|
return block, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *buddy) getBlock(tier int) (Block, error) {
|
||
|
|
if tier > f.top {
|
||
|
|
return Block{}, ErrOutOfMemory
|
||
|
|
}
|
||
|
|
|
||
|
|
if bucket := f.free[tier]; len(bucket) > 0 {
|
||
|
|
lastIdx := len(bucket) - 1
|
||
|
|
block := bucket[lastIdx]
|
||
|
|
f.free[tier] = bucket[:lastIdx]
|
||
|
|
return block, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
split, err := f.getBlock(tier + 1)
|
||
|
|
if err != nil {
|
||
|
|
return Block{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
size := split.Size / 2
|
||
|
|
f.free[tier] = append(f.free[tier], Block{
|
||
|
|
Offset: split.Offset + size,
|
||
|
|
Size: size,
|
||
|
|
})
|
||
|
|
return Block{
|
||
|
|
Offset: split.Offset,
|
||
|
|
Size: size,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *buddy) Free(offset int) error {
|
||
|
|
size, exists := f.alloc[offset]
|
||
|
|
if !exists {
|
||
|
|
return ErrInvalidFree
|
||
|
|
}
|
||
|
|
|
||
|
|
freed := Block{
|
||
|
|
Offset: offset,
|
||
|
|
Size: size,
|
||
|
|
}
|
||
|
|
|
||
|
|
tier := GetBucketTier(size)
|
||
|
|
f.free[tier] = append(f.free[tier], freed)
|
||
|
|
|
||
|
|
// mark as free
|
||
|
|
delete(f.alloc, offset)
|
||
|
|
|
||
|
|
// merge buddies
|
||
|
|
f.merge(tier, freed, len(f.free[tier])-1)
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *buddy) merge(tier int, block Block, blockIdx int) {
|
||
|
|
// nothing to merge at the top tier
|
||
|
|
if tier >= f.top {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
level := f.free[tier]
|
||
|
|
|
||
|
|
// figure out the offset of our buddy block, and the resulting offset of a merge
|
||
|
|
buddyOffset := 0
|
||
|
|
mergedOffset := 0
|
||
|
|
if block.Offset%(2*block.Size) == 0 {
|
||
|
|
// we are an even block, buddy is after
|
||
|
|
buddyOffset = block.Offset + block.Size
|
||
|
|
mergedOffset = block.Offset
|
||
|
|
} else {
|
||
|
|
// we are an odd block, buddy is before
|
||
|
|
buddyOffset = block.Offset - block.Size
|
||
|
|
mergedOffset = buddyOffset
|
||
|
|
}
|
||
|
|
|
||
|
|
// check if buddy block is allocated
|
||
|
|
if _, allocated := f.alloc[buddyOffset]; allocated {
|
||
|
|
// yes - then we can't merge
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// find the free list index of the buddy
|
||
|
|
var buddyIdx int
|
||
|
|
for candidateIdx, candidate := range level {
|
||
|
|
if candidate.Offset == buddyOffset {
|
||
|
|
buddyIdx = candidateIdx
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove both blocks from free list
|
||
|
|
// todo: implement using a linked list
|
||
|
|
if buddyIdx > blockIdx {
|
||
|
|
f.free[tier] = append(append(level[:blockIdx], level[blockIdx+1:buddyIdx]...), level[buddyIdx+1:]...)
|
||
|
|
} else {
|
||
|
|
f.free[tier] = append(append(level[:buddyIdx], level[buddyIdx+1:blockIdx]...), level[blockIdx+1:]...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// add the merged block to free list, on the next tier
|
||
|
|
merged := Block{
|
||
|
|
Offset: mergedOffset,
|
||
|
|
Size: 2 * block.Size,
|
||
|
|
}
|
||
|
|
f.free[tier+1] = append(f.free[tier+1], merged)
|
||
|
|
|
||
|
|
// attempt to merge next level
|
||
|
|
f.merge(tier+1, merged, len(f.free[tier+1])-1)
|
||
|
|
}
|
||
|
|
|
||
|
|
func GetBucketTier(size int) int {
|
||
|
|
tier := int(math.Log2(float64(size-1))) + 1
|
||
|
|
|
||
|
|
tier -= minBucketTier
|
||
|
|
if tier < 0 {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
return tier
|
||
|
|
}
|
||
|
|
|
||
|
|
func IsPowerOfTwo(n int) bool {
|
||
|
|
return n > 0 && (n&(n-1)) == 0
|
||
|
|
}
|