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
							 | 
						||
| 
								 | 
							
								}
							 |