-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
-- Pool Allocator | ||
-- | ||
-- The pool allocator allocate chunks from fixed contiguous buffer of many chunks, allocations | ||
-- pops a free chunk from the pool and deallocations pushes a chunk back. It works | ||
-- by using a single linked list of free chunks. | ||
-- | ||
-- The purpose of this allocator is to have very fast allocations with almost | ||
-- no runtime cost when the maximum used space is known ahead. | ||
-- | ||
-- Reallocations and deallocations free space (unlikely the Arena allocator). | ||
-- Reallocations greater than the chunk size will always fails. | ||
-- | ||
-- The implementation is based on https://www.gingerbill.org/article/2019/02/16/memory-allocation-strategies-004/ | ||
|
||
require 'memory' | ||
require 'allocators.interface' | ||
|
||
## local make_pool_allocator = generalize(function(T, Size, error_on_failure) | ||
##[[ | ||
staticassert(traits.is_number(Size), 'PoolAllocator: size must be a number') | ||
staticassert(Size > 0, 'PoolAllocator: size must be greater than 0') | ||
staticassert(traits.is_type(T), 'PoolAllocator: T must be a valid type') | ||
staticassert(T.size >= primtypes.pointer.size, 'PoolAllocator: T size must be at least a pointer in size') | ||
staticassert(T.align & (primtypes.pointer.size-1) == 0, 'PoolAllocator: T must be aligned to pointers') | ||
]] | ||
|
||
local T = #[T]# | ||
local PoolFreeNode = @record{ | ||
next: PoolFreeNode* | ||
} | ||
local PoolAllocatorT = @record{ | ||
initialized: boolean, | ||
head: PoolFreeNode*, | ||
buffer: T[#[Size]#] | ||
} | ||
|
||
-- Free all allocations. | ||
function PoolAllocatorT:dealloc_all() <noinline> | ||
self.head = nilptr | ||
-- link all free nodes in reverse order | ||
for i:isize=#self.buffer-1,0,-1 do | ||
local node: PoolFreeNode* = (@PoolFreeNode*)(&self.buffer[i]) | ||
node.next = self.head | ||
self.head = node | ||
end | ||
end | ||
|
||
function PoolAllocatorT:dealloc(p: pointer) | ||
if unlikely(p == nilptr) then return end | ||
-- is this pointer really valid? | ||
local offset: usize = (@usize)(p) - (@usize)(&self.buffer[0]) | ||
check(offset // #T < #self.buffer and offset % #T == 0, 'PoolAllocator.dealloc: pointer not in buffer bounds') | ||
-- push free node | ||
local node: PoolFreeNode* = (@PoolFreeNode*)(p) | ||
node.next = self.head | ||
self.head = node | ||
end | ||
|
||
-- Initialize the pool allocator. | ||
-- There is not need to call this if zero initialized, it's called automatically on first alloc. | ||
function PoolAllocatorT:init() | ||
self.initialized = true | ||
self:dealloc_all() | ||
end | ||
|
||
function PoolAllocatorT:alloc(size: usize): pointer | ||
if unlikely(size > #T) then | ||
## if error_on_failure then | ||
error('PoolAllocator.alloc: attempt to allocate a size greater than chunk size') | ||
## end | ||
return nilptr | ||
end | ||
-- get the latest free node | ||
local node: PoolFreeNode* = self.head | ||
-- the node will be nilptr if not initialized or out of memory | ||
if unlikely(node == nilptr) then | ||
if not self.initialized then | ||
-- first initialization | ||
self:init() | ||
node = self.head | ||
else | ||
-- out of memory | ||
## if error_on_failure then | ||
error('PoolAllocator.alloc: out of memory') | ||
## end | ||
return nilptr | ||
end | ||
end | ||
-- pop free node | ||
self.head = node.next | ||
-- the node is now actually the allocated chunk | ||
return node | ||
end | ||
|
||
function PoolAllocatorT:alloc0(size: usize): pointer | ||
local p: pointer = self:alloc(size) | ||
if likely(p ~= nilptr and size ~= 0) then | ||
memory.zero(p, size) | ||
end | ||
return p | ||
end | ||
|
||
function PoolAllocatorT:realloc(p: pointer, size: usize): pointer | ||
if unlikely(p == nilptr) then | ||
return self:alloc(size) | ||
elseif unlikely(size == 0) then | ||
self:dealloc(p) | ||
return nilptr | ||
elseif unlikely(size > #T) then | ||
## if error_on_failure then | ||
error('PoolAllocator.realloc: attempt to allocate a size greater than chunk size') | ||
## end | ||
return nilptr | ||
else | ||
return p | ||
end | ||
end | ||
|
||
function PoolAllocatorT:realloc0(p: pointer, newsize: usize, oldsize: usize): pointer | ||
p = self:realloc(p, newsize) | ||
if likely(newsize > oldsize and p ~= nilptr) then | ||
-- zero the grown part | ||
memory.zero(&(@byte[0]*)(p)[oldsize], newsize - oldsize) | ||
end | ||
return p | ||
end | ||
|
||
## implement_allocator_interface(PoolAllocatorT) | ||
|
||
## return PoolAllocatorT | ||
## end) | ||
|
||
global PoolAllocator: type = #[make_pool_allocator]# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters