Add mem.Buddy_Allocator

This commit is contained in:
gingerBill
2024-03-06 16:14:45 +00:00
parent 792f06a234
commit 2a0543d2f0

View File

@@ -862,3 +862,267 @@ panic_allocator :: proc() -> Allocator {
}
Buddy_Block :: struct #align(align_of(uint)) {
size: uint,
is_free: bool,
}
@(require_results)
buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block {
return (^Buddy_Block)(([^]byte)(block)[block.size:])
}
@(require_results)
buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block {
block := block
if block != nil && size != 0 {
// Recursive Split
for size < block.size {
sz := block.size >> 1
block.size = sz
block = buddy_block_next(block)
block.size = sz
block.is_free = true
}
if size <= block.size {
return block
}
}
// Block cannot fit the requested allocation size
return nil
}
buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) {
for {
// Keep looping until there are no more buddies to coalesce
block := head
buddy := buddy_block_next(block)
no_coalescence := true
for block < tail && buddy < tail { // make sure the buddies are within the range
if block.is_free && buddy.is_free && block.size == buddy.size {
// Coalesce buddies into one
block.size <<= 1
block = buddy_block_next(block)
if block < tail {
buddy = buddy_block_next(block)
no_coalescence = false
}
} else if block.size < buddy.size {
// The buddy block is split into smaller blocks
block = buddy
buddy = buddy_block_next(buddy)
} else {
block = buddy_block_next(buddy)
if block < tail {
// Leave the buddy block for the next iteration
buddy = buddy_block_next(block)
}
}
}
if no_coalescence {
return
}
}
}
@(require_results)
buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block {
assert(size != 0)
best_block: ^Buddy_Block
block := head // left
buddy := buddy_block_next(block) // right
// The entire memory section between head and tail is free,
// just call 'buddy_block_split' to get the allocation
if buddy == tail && block.is_free {
return buddy_block_split(block, size)
}
// Find the block which is the 'best_block' to requested allocation sized
for block < tail && buddy < tail { // make sure the buddies are within the range
// If both buddies are free, coalesce them together
// NOTE: this is an optimization to reduce fragmentation
// this could be completely ignored
if block.is_free && buddy.is_free && block.size == buddy.size {
block.size <<= 1
if size <= block.size && (best_block == nil || block.size <= best_block.size) {
best_block = block
}
block = buddy_block_next(buddy)
if block < tail {
// Delay the buddy block for the next iteration
buddy = buddy_block_next(block)
}
continue
}
if block.is_free && size <= block.size &&
(best_block == nil || block.size <= best_block.size) {
best_block = block
}
if buddy.is_free && size <= buddy.size &&
(best_block == nil || buddy.size < best_block.size) {
// If each buddy are the same size, then it makes more sense
// to pick the buddy as it "bounces around" less
best_block = buddy
}
if (block.size <= buddy.size) {
block = buddy_block_next(buddy)
if (block < tail) {
// Delay the buddy block for the next iteration
buddy = buddy_block_next(block)
}
} else {
// Buddy was split into smaller blocks
block = buddy
buddy = buddy_block_next(buddy)
}
}
if best_block != nil {
// This will handle the case if the 'best_block' is also the perfect fit
return buddy_block_split(best_block, size)
}
// Maybe out of memory
return nil
}
Buddy_Allocator :: struct {
head: ^Buddy_Block,
tail: ^Buddy_Block,
alignment: uint,
}
buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator {
return Allocator{
procedure = buddy_allocator_proc,
data = b,
}
}
buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint) {
assert(data != nil)
assert(is_power_of_two(uintptr(len(data))))
assert(is_power_of_two(uintptr(alignment)))
alignment := alignment
if alignment < size_of(Buddy_Block) {
alignment = size_of(Buddy_Block)
}
ptr := raw_data(data)
assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment")
b.head = (^Buddy_Block)(ptr)
b.head.size = len(data)
b.head.is_free = true
b.tail = buddy_block_next(b.head)
b.alignment = alignment
}
@(require_results)
buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint {
size := size
actual_size := b.alignment
size += size_of(Buddy_Block)
size = align_forward_uint(size, b.alignment)
for size > actual_size {
actual_size <<= 1
}
return actual_size
}
@(require_results)
buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint, zeroed: bool) -> ([]byte, Allocator_Error) {
if size != 0 {
actual_size := buddy_block_size_required(b, size)
found := buddy_block_find_best(b.head, b.tail, actual_size)
if found != nil {
// Try to coalesce all the free buddy blocks and then search again
buddy_block_coalescence(b.head, b.tail)
found = buddy_block_find_best(b.head, b.tail, actual_size)
}
if found == nil {
return nil, .Out_Of_Memory
}
found.is_free = false
data := ([^]byte)(found)[b.alignment:][:size]
if zeroed {
zero_slice(data)
}
return data, nil
}
return nil, nil
}
buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error {
if ptr != nil {
if !(b.head <= ptr && ptr <= b.tail) {
return .Invalid_Pointer
}
block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:])
block.is_free = true
buddy_block_coalescence(b.head, b.tail)
}
return nil
}
buddy_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) {
b := (^Buddy_Allocator)(allocator_data)
switch mode {
case .Alloc, .Alloc_Non_Zeroed:
return buddy_allocator_alloc(b, uint(size), mode == .Alloc)
case .Resize:
return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b))
case .Resize_Non_Zeroed:
return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b))
case .Free:
return nil, buddy_allocator_free(b, old_memory)
case .Free_All:
alignment := b.alignment
head := ([^]byte)(b.head)
tail := ([^]byte)(b.tail)
data := head[:ptr_sub(tail, head)]
buddy_allocator_init(b, data, alignment)
case .Query_Features:
set := (^Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Query_Features, .Alloc, .Alloc_Non_Zeroed, .Resize, .Resize_Non_Zeroed, .Free, .Free_All}
}
return nil, nil
case .Query_Info:
return nil, .Mode_Not_Implemented
}
return nil, nil
}